source("https://raw.githubusercontent.com/MatteoFasulo/Rare-Earth/main/R/util/coreFunctions.R")

loadPackages(c('sn','tidyverse','psych','RColorBrewer','stargazer','mclust','ContaminatedMixt',
               'plotly','ggplot2','ggdendro','teigen','tclust','HDMD','caTools','clustvarsel',
               'vscc','sparcl','pgmm','caret','glmnet','MLmetrics','DT'))

load("Z:\\DesktopC\\LUMSA\\2\\Data Mining\\Finite Mixture\\FiniteMixtureL31.RData")
rm(CO2data)
rm(NOdata)
rm(tonedata)
type <- wine$Type
rm(wine)

1 Introduzione

Dati su 27 caratteristiche chimico/fisiche di tre diversi tipi di vino (Barolo, Grignolino, Barbera) dal Piemonte. Un set di dati con 178 osservazioni e 28 variabili (di cui la prima relativa alla tipologia di vino). Nell’ordine:

  • Barolo
  • Grignolino
  • Barbera
data(wines)
wines

1.1 Annata del vino: un fattore da considerare?

E’ stato possibile attraverso la ricerca originaria risalire all’anno di osservazione di ciascun vino. Di seguito vengono riportate le osservazioni dei tre diversi tipi di vino durante gli anni:

year <- as.numeric(substr(rownames(wines), 6, 7))
table(wines$wine, year)
            year
             70 71 72 73 74 75 76 78 79
  Barolo      0 19  0 20 20  0  0  0  0
  Grignolino  9  9  7  9 16  9 12  0  0
  Barbera     0  0  0  0  9  0  5 29  5
#wines[,'wine'] <- type

Notiamo subito che il Barbera è distribuito principalmente negli ultimi anni (76,78,79) mentre il Barolo nel 71, 73 e 74. Per quanto riguarda la percentuale delle singole classi:

wines %>%
  count(wine = factor(wine)) %>%
  mutate(pct = prop.table(n)) %>% 
  ggplot(aes(x = wine, y = pct, fill = wine, label = scales::percent(pct))) + 
  geom_col(position = 'dodge') + 
  geom_text(position = position_dodge(width = .9),
            vjust = -0.5, 
            size = 3) + 
  scale_y_continuous(name = "Percentage")+
  scale_x_discrete(name = "Wine Name")+
  scale_fill_hue(l=40, c=35)+
  theme(legend.position = "none")

E’ chiaro che il Grignolino sia il più numeroso (39.9%) seguito dal Barolo (33.1%) e dal Barbera (27.0%).

Per rappresentare la dispersione dei dati abbiamo usato uno scatterplot leggermente differente dal solito. Sulla diagonale superiore si vede la distribuzione dei dati mentre sulla diagonale inferiore vi è la correlazione di Pearson tra le variabili.

# Correlation panel
my_cols <- c("#00AFBB", "#E7B800", "#FC4E07") 
panel.cor <- function(x, y){
    usr <- par("usr"); on.exit(par(usr))
    par(usr = c(0, 1, 0, 1))
    r <- round(cor(x, y), digits=2)
    txt <- paste0("R = ", r)
    text(0.5, 0.5, txt, cex = 1)
}
# Customize upper panel
upper.panel<-function(x, y){
  points(x,y, pch = 19, col = my_cols[wines$wine],cex=.5)
}
# Create the plots
pairs(wines[,2:8], 
      lower.panel = panel.cor,
      upper.panel = upper.panel)

Dalle prime 7 variabili, è possibile notare una correlazione di 0.69 tra acidity e malic e ovviamente una relazione inversamente proporzionale tra pH e acidity.

2 Statistiche descrittive

Per visualizzare le statistiche descrittive (media e deviazione standard) ci è sembrato opportuno dividerle in base alla classe di appartenenza:

printMeanAndSdByGroup <- function(variables,groupvariable)
  {
     variablenames <- c(names(groupvariable),names(as.data.frame(variables)))
     groupvariable <- groupvariable[,1]
     means <- aggregate(as.matrix(variables) ~ groupvariable, FUN = mean)
     names(means) <- variablenames
     print(paste("Means:"))
     print(means)
     sds <- aggregate(as.matrix(variables) ~ groupvariable, FUN = sd)
     names(sds) <- variablenames
     print(paste("Standard deviations:"))
     print(sds)
}
printMeanAndSdByGroup(wines[2:28],wines[1])
[1] "Means:"
[1] "Standard deviations:"

Alcune considerazioni:

  • La media di sugar, potassium, magnesium, phosphate, chloride, flavanoids, proanthocyanins, colour nel Barolo è più alta.
  • La media di acidity, tartaric, malic, uronic, alcal_ash nel Barbera è più alta.
  • La deviazione standard di acidity è più alta nel Grignolino.

2.1 Varianza Within

Abbiamo calcolato la varianza within tra una feature e i tipi di vino:

calcWithinGroupsVariance(wines["flavanoids"],wines[1])
[1] 0.2747075

2.2 Varianza Between

Stesso discorso per la varianza between tra una feature e i vini:

calcBetweenGroupsVariance <- function(variable,groupvariable)
  {
     # find out how many values the group variable can take
     groupvariable2 <- as.factor(groupvariable[[1]])
     levels <- levels(groupvariable2)
     numlevels <- length(levels)
     # calculate the overall grand mean:
     grandmean <- sapply(variable,mean)
     # get the mean and standard deviation for each group:
     numtotal <- 0
     denomtotal <- 0
     for (i in 1:numlevels)
     {
        leveli <- levels[i]
        levelidata <- variable[groupvariable==leveli,]
        levelilength <- length(levelidata)
        # get the mean and standard deviation for group i:
        meani <- mean(levelidata)
        sdi <- sd(levelidata)
        numi <- levelilength * ((meani - grandmean)^2)
        denomi <- levelilength
        numtotal <- numtotal + numi
        denomtotal <- denomtotal + denomi
     }
     # calculate the between-groups variance
     Vb <- numtotal / (numlevels - 1)
     Vb <- Vb[[1]]
     return(Vb)
}
calcBetweenGroupsVariance(wines["flavanoids"],wines[1])
[1] 64.2612

2.3 Separazione

Per vedere quali variabili hanno la maggiore separazione, (rapporto tra Varianza Between e Varianza Within) abbiamo scritto una funzione apposita per calcolarne il valore per ogni feature.

3 Splitting in Train e Test

Per alcune analisi successive, abbiamo provato a cambiare il task della ricerca tentando di utilizzare un modello come classificatore e di misurarne le prestazioni di classificazione. A tale scopo, abbiamo suddiviso il dataset originario in due sottogruppi:

  • train
  • test
require(caTools)
sample = sample.split(wines[,1], SplitRatio = .50)

train = subset(wines, sample == TRUE)
trainTestNames <- train$wine
print(paste("Train Obs:",nrow(train)))
[1] "Train Obs: 90"
#train$wine <- as.numeric(train$wine)

test  = subset(wines, sample == FALSE)
wineTestNames <- test$wine
print(paste("Test Obs:",nrow(test)))
[1] "Test Obs: 88"
#test$wine <- as.numeric(test$wine)

4 Clustering

Per il Clustering abbiamo deciso di applicare:

  • Un approccio Distance-Based:
    • Gerarchico:
      • Euclidean, Minkowski, Manhattan, Mahalanobis
    • Di partizionamento:
      • KMeans
      • PAM
  • Un approccio Model-Based:
    • Gaussian Mixture (Mclust)
    • Contaminated Normal (CNmixt)
    • Multivariate t Distribution (teigen)
    • Parsimonious Gaussian Mixture Models (pgmm)

4.1 Distance-Based

dissMatrix    <- pairwise.mahalanobis(wines[,-1],
                                      grouping = c(1:nrow(wines)),
                                      cov = cov(wines[,-1]))$distance
dissMatrix <- sqrt(dissMatrix)
dissMatrix <- as.dist(dissMatrix)
combDist <- function(distance, methods, df, dt, dissMatrix) {
  c <- 0
  results <- list()
  for (i in 1:length(distance)){
    ifelse(distance[i] == "minkowski",
           dist <- dist(df, method = distance[i], p = 4),
           ifelse(distance[i] == "mahalanobis",
                  dist <- dissMatrix,
                  dist <- dist(df, method = distance[i])))
    for (j in 1:length(methods)){
      dendo = hclust(dist, method = methods[j])
      dendo.x = ggdendrogram(dendo, rotate = F, size = 2, leaf_labels = T, labels = F) + 
                               ggtitle(paste(distance[i],' ',methods[j],sep=''))
      for(elem in 2:4){
        cluster = cutree(dendo, k=elem)
        c <- c + 1
        results[[c]] <- list(distance = distance[i],
                             method = methods[j],
                             groups = elem,
                             table = table(dt,cluster),
                             dendo = dendo.x,
                             AdjustedRandIndex = adjustedRandIndex(dt,cluster),
                             cluster = cluster)
      }
    }
  }
  return(results)
}
results <- combDist(c("euclidean", "manhattan", "minkowski","mahalanobis"),
                    c("single", "complete", "average", "ward.D"), scale(wines[,-1]), wines[,1], dissMatrix)

optimal <- function(results){
  best_randIndex.eu = 0
  best_randIndex.ma = 0
  best_randIndex.mi = 0
  best_randIndex.maha = 0
  best_model.eu = integer()
  best_model.ma = integer()
  best_model.mi = integer()
  best_model.maha = integer()
  for (i in 1:length(results)){
    current_randIndex = results[[i]]$AdjustedRandIndex
    if (results[[i]]$distance == "euclidean"){
      if (current_randIndex > best_randIndex.eu) {
        best_randIndex.eu = current_randIndex
        best_model.eu = i
      }
    }
    else if (results[[i]]$distance == "manhattan"){
      if (current_randIndex > best_randIndex.ma) {
        best_randIndex.ma = current_randIndex
        best_model.ma = i
      }
    }
    else if (results[[i]]$distance == "minkowski"){
      if (current_randIndex > best_randIndex.mi) {
        best_randIndex.mi = current_randIndex
        best_model.mi = i
      }
    }
    else if (results[[i]]$distance == "mahalanobis"){
      if (current_randIndex > best_randIndex.maha) {
        best_randIndex.maha = current_randIndex
        best_model.maha = i
      }
    }
  }
  #print(list(euclidean = list(model.number = best_model.eu,
  #                            cluster = results[[best_model.eu]]$groups,
  #                            AdjustedRandIndex = best_randIndex.eu),
  #           manhattan = list(model.number = best_model.ma,
  #                            cluster = results[[best_model.ma]]$groups,
  #                            AdjustedRandIndex = best_randIndex.ma),
  #           minkowski = list(model.number = best_model.mi,
  #                            cluster = results[[best_model.mi]]$groups,
  #                            AdjustedRandIndex = best_randIndex.mi),
  #           mahalanobis=list(model.number = best_model.maha,
  #                            cluster = results[[best_model.maha]]$groups,
  #                            AdjustedRandIndex = best_randIndex.maha))
  #    )
  return(list(euclidean = results[[best_model.eu]],
              manhattan = results[[best_model.ma]],
              minkowski = results[[best_model.mi]],
              mahalanobis=results[[best_model.maha]]))
}
best_dist_model = optimal(results)

4.1.1 Dendrogrammi

4.1.1.1 Euclidean

print(paste("AdjustedRandIndex:",round(best_dist_model$euclidean$AdjustedRandIndex,3)))
[1] "AdjustedRandIndex: 0.769"

4.1.1.2 Manhattan

print(paste("AdjustedRandIndex:",round(best_dist_model$manhattan$AdjustedRandIndex,3)))
[1] "AdjustedRandIndex: 0.946"

4.1.1.3 Minkowski

ggplotly(best_dist_model$minkowski$dendo)
print(best_dist_model$minkowski$table)
            cluster
dt            1  2  3  4
  Barolo     55  2  2  0
  Grignolino  8  1 59  3
  Barbera     2  0  0 46
print(paste("AdjustedRandIndex:",round(best_dist_model$minkowski$AdjustedRandIndex,3)))
[1] "AdjustedRandIndex: 0.73"

4.1.1.4 Mahalanobis

print(paste("AdjustedRandIndex:",round(best_dist_model$mahalanobis$AdjustedRandIndex,4)))
[1] "AdjustedRandIndex: 0.27"

4.1.2 K-Means e PAM

4.1.2.1 KMeans 3

require(cluster)
k.means.3 <- kmeans(scale(wines[,-1]),centers=3,nstart = 50, iter.max = 100)
table(wines[,1], k.means.3$cluster)
            
              1  2  3
  Barolo      1  0 58
  Grignolino 68  1  2
  Barbera     0 48  0
print(paste("AdjustedRandIndex:",round(adjustedRandIndex(k.means.3$cluster, wines[,1]),3)))
[1] "AdjustedRandIndex: 0.93"

4.1.2.2 KMeans 4

require(cluster)
k.means.4 <- kmeans(scale(wines[,-1]),centers=4,nstart = 50, iter.max = 100)
table(wines[,1], k.means.4$cluster)
            
              1  2  3  4
  Barolo      0  0 59  0
  Grignolino 38  0  5 28
  Barbera     0 47  0  1
print(paste("AdjustedRandIndex:",round(adjustedRandIndex(k.means.4$cluster, wines[,1]),3)))
[1] "AdjustedRandIndex: 0.736"

4.1.2.3 PAM 3

require(cluster)
PAM.3 <- pam(wines[,-1], k=3,
    metric = "euclidean", 
    nstart = 50,
    stand = TRUE)
table(wines[,1], PAM.3$clustering)
            
              1  2  3
  Barolo     50  9  0
  Grignolino  3 67  1
  Barbera     1  1 46
print(paste("AdjustedRandIndex:",round(adjustedRandIndex(PAM.3$clustering, wines[,1]),3)))
[1] "AdjustedRandIndex: 0.754"

4.1.2.4 PAM 4

require(cluster)
PAM.4 <- pam(wines[,-1], k=4,
    metric = "euclidean", 
    nstart = 50,
    stand = TRUE)
table(wines[,1], PAM.4$clustering)
            
              1  2  3  4
  Barolo     28 27  4  0
  Grignolino  3  0 67  1
  Barbera     1  0  1 46
print(paste("AdjustedRandIndex:",round(adjustedRandIndex(PAM.4$clustering, wines[,1]),3)))
[1] "AdjustedRandIndex: 0.728"

5 Variable Selection

5.1 Principio filosofico

Novacula Occami: frustra fit per plura quod potest fieri per pauciora (Il rasoio di Occam: è futile fare con più mezzi ciò che si può fare con meno). Tale principio metodologico è ritenuto alla base del pensiero scientifico moderno.

5.1.1 Headlong

            
              1  2  3  4
  Barolo     47 10  2  0
  Grignolino  6  3 61  1
  Barbera     0  0  1 47
[1] "AdjustedRandIndex: 0.734"

5.1.2 Greedy

            
              1  2  3  4
  Barolo     47 10  2  0
  Grignolino  6  3 61  1
  Barbera     0  0  1 47
[1] "AdjustedRandIndex: 0.734"

5.1.3 VSCC

table(wines[,1], vscc.mclust$initialrun$classification) #Clustering results on full data set
            
              1  2  3
  Barolo     58  1  0
  Grignolino  1 70  0
  Barbera     0  2 46
print(paste("AdjustedRandIndex:",round(adjustedRandIndex(vscc.mclust$initialrun$classification, wines[,1]),3)))
[1] "AdjustedRandIndex: 0.931"
table(wines[,1], vscc.mclust$bestmodel$classification) #Clustering results on reduced data set
            
              1  2  3
  Barolo     59  0  0
  Grignolino  3 66  2
  Barbera     0  0 48
print(paste("AdjustedRandIndex:",round(adjustedRandIndex(vscc.mclust$bestmodel$classification, wines[,1]),3)))
[1] "AdjustedRandIndex: 0.913"

5.1.4 KMeansSparse 3

5.1.5 KMeansSparse 4

5.2 Model-Based

mixt.wines <- Mclust(wines[,-1],G=3:8)

cn.wines.mixt <- CNmixt(wines[,-1], G = 3, initialization = "mixt", seed = 1234, parallel = F, verbose = F)
cn.wines.kmeans <- CNmixt(wines[,-1], G = 3, initialization = "kmeans", seed = 1234, parallel = F, verbose = F)
cn.wines.rpost <- CNmixt(wines[,-1], G = 3, initialization = "random.post", seed = 1234, parallel = F, verbose = F)
cn.wines.rclass <- CNmixt(wines[,-1], G = 3, initialization = "random.clas", seed = 1234, parallel = F, verbose = F)


teigen.kmeans <- teigen(wines[,-1], Gs=3:4, init = 'kmeans', scale = T)
adjustedRandIndex(wines[,1],teigen.kmeans$classification)

teigen.classifier.kmeans <- teigen(train[,-1], Gs=3:4, init = 'kmeans', scale = T, known = train[,1])
predict(teigen.classifier.kmeans,test[,-1])
adjustedRandIndex(test[,1],predict(teigen.classifier.kmeans, test[,-1])$classification)

teigen.classifier.uniform <- teigen(train[,-1], Gs=3:4, init = 'uniform', scale = T, known = train[,1])
predict(teigen.classifier.uniform,test[,-1])
adjustedRandIndex(test[,1],predict(teigen.classifier.uniform, test[,-1])$classification)

teigen.hard <- teigen(wines[,-1], Gs=3:4, init = 'hard', scale = T)
adjustedRandIndex(wines[,1],teigen.hard$classification)
teigen.soft <- teigen(wines[,-1], Gs=3:4, init = 'soft', scale = T)
adjustedRandIndex(wines[,1],teigen.soft$classification)

6 Tecniche di regolarizzazione

Per la nostra analisi abbiamo voluto verificare l’efficienza di tre noti modelli di regolarizzazione attraverso il pacchetto caret:

  • Ridge
  • Lasso
  • Elastic Net
lambda <- 10^seq(0, -2, length = 250)

6.1 Ridge

Il modello Ridge riduce i coefficienti, in modo che le variabili, con un contributo minore al risultato, abbiano i loro coefficienti vicini allo zero. Invece di forzarli a essere esattamente zero (come nel Lasso), li penalizziamo con un termine chiamato norma L2 costringendoli così a essere piccoli. In questo modo diminuiamo la complessità del modello senza eliminare nessuna variabile attraverso una costante chiamata lambda (\(\lambda\)) di penalizzazione: \[ L_{ridge}(\hat{\beta}) = \sum_{i = 1}^{n}{(y_i - x_i\hat{\beta})^2} + \lambda\sum_{k = 1}^{K}{\hat{\beta}_k^2} \]

# Build the model
set.seed(123)
ridge <- caret::train(
  x = train[,-1],
  y = factor(train[,1]),
  method = "glmnet",
  trControl = trainControl("cv", number = 10, classProbs = TRUE, summaryFunction = multiClassSummary),
  tuneGrid = expand.grid(alpha = 0, lambda=lambda),
  metric="Accuracy")
# Model coefficients
coef(ridge$finalModel, ridge$bestTune$lambda)
# Make predictions
predictions.ridge <- ridge %>% predict(test)
# Model prediction performance
tibble(
  trueValue = wineTestNames,
  predictedValue = predictions.ridge)

Il ridge è composto dalla somma dei residui quadrati più una penalità, definita dalla lettera Lambda, che è moltiplicata per la somma dei coefficienti quadrati \(\beta\). Quando \(\lambda = 0\), il termine di penalità non ha alcun effetto e il ridge produrrà i coefficienti minimi quadrati classici. Tuttavia, quando \(\lambda\) aumenta all’infinito, l’impatto della penalità aumenta e i coefficienti si avvicinano allo zero. Il ridge è particolarmente indicato quando si hanno molti dati multivariati con numero di feature maggiore del numero di osservazioni. Lo svantaggio, però, è che includerà tutti le feature nel modello finale, a differenza dei metodi di feature selection, che generalmente selezioneranno un insieme ridotto di variabili tra quelle disponibili.

caret::confusionMatrix(predictions.ridge, test$wine)

6.2 Lasso

Il Least Absolute Shrinkage and Selection Operator (LASSO) riduce i coefficienti verso lo zero penalizzando il modello con un termine di penalità chiamato norma L1, che è la somma dei coefficienti in valore assoluto: \[ L_{lasso}(\hat{\beta}) = \sum_{i = 1}^{n}{(y_i - x_i\hat{\beta})^2} + \lambda\sum_{k = 1}^{K}{|\hat{\beta}_k|} \]

# Build the model
set.seed(123)
lasso <- caret::train(
  x = train[,-1],
  y = factor(train[,1]),
  method = "glmnet",
  trControl = trainControl("cv", number = 10, classProbs = TRUE, summaryFunction = multiClassSummary),
  tuneGrid = expand.grid(alpha = 1, lambda=lambda),
  metric="Accuracy")
# Model coefficients
coef(lasso$finalModel, lasso$bestTune$lambda)
# Make predictions
predictions.lasso <- lasso %>% predict(test)
# Model prediction performance
tibble(
  trueValue = wineTestNames,
  predictedValue = predictions.lasso)

In questo caso la penalità ha l’effetto di forzare alcune delle stime dei coefficienti, con un contributo minore al modello, a essere esattamente uguale a zero. Il lasso, quindi, può anche essere visto come un’alternativa ai metodi di feature selection per eseguire la selezione delle variabili al fine di ridurre la complessità del modello.

Come nel ridge, è fondamentale selezionare un buon valore di \(\lambda\).

Quando lambda è piccolo, il risultato è molto vicino alla stima dei minimi quadrati. All’aumentare di lambda, si verifica una contrazione in modo da poter eliminare le variabili che sono a zero.

caret::confusionMatrix(predictions.lasso, test$wine)

6.3 Elastic Net

Elastic Net combina le proprietà di Ridge e Lasso penalizzando il modello usando sia la norma L2 che la norma L1: \[ L_{ElasticNet}(\hat{\beta}) = \frac{\sum_{i = 1}^{n}{(y_i - x_i\hat{\beta})^2}}{2n} + \lambda(\frac{1-\alpha}{2}\sum_{k = 1}^{K}{\hat{\beta}_k^2} + \alpha\sum_{k = 1}^{K}{|\hat{\beta}_k}|) \]

set.seed(123)
elastic <- caret::train(
  x = train[,-1],
  y = factor(train[,1]),
  method = "glmnet",
  trControl = trainControl("cv", number = 10, classProbs = TRUE, summaryFunction = multiClassSummary),
  metric="Accuracy")
# Model coefficients
coef(elastic$finalModel, elastic$bestTune$lambda)
# Make predictions
predictions.enet <- elastic %>% predict(test)
# Model prediction performance
tibble(
  trueValue = wineTestNames,
  predictedValue = predictions.enet)

Oltre a impostare e scegliere un valore lambda, l’elastic net ci consente anche di ottimizzare il parametro alfa dove \(\alpha = 0\) corrisponde a ridge e \(\alpha = 1\) al lasso.

Pertanto possiamo scegliere un valore \(\alpha\) compreso tra 0 e 1 per ottimizzare l’elastic net. Se tale valore è incluso in questo intervallo, si avrà una riduzione con alcuni portati a \(0\).

caret::confusionMatrix(predictions.enet, test$wine)

6.4 Summary delle prestazioni

models <- list(ridge = caret::confusionMatrix(predictions.ridge, test$wine), 
               lasso = caret::confusionMatrix(predictions.lasso,test$wine),
               elastic = caret::confusionMatrix(predictions.enet,test$wine))
#models %>% summary(metric = "Accuracy")

7 Parsimonious Gaussian Mixture Models

# Il rilassamento dato da (p-q)^2 > p+q è verificato per  1 <= q <= 20
pg.random.ccc <- pgmmEM(selectedWines[,-1], rG=3:4, rq=1:5, seed = 1234, relax = T)
adjustedRandIndex(wines[,1],pg.random.ccc$map)

pg.random <- pgmmEM(wines[,-1], rG=3:4, rq=20:25,icl=F,zstart = 1,cccStart = F,loop = 50, seed = 1234, relax = T)
adjustedRandIndex(wines[,1],pg.random.ccc$map)

pg2 <- pgmmEM(wines[,-1], rG=3:4, rq=20:25,icl=T,zstart = 1,cccStart = T,loop = 50, seed = 1234, relax = T)
adjustedRandIndex(wines[,1],pg2$map)

pg2 <- pgmmEM(wines[,-1], rG=3:4, rq=20:25,icl=T,zstart = 2, seed = 1234, relax = T)
adjustedRandIndex(wines[,1],pg2$map)

pg2 <- pgmmEM(wines[,-1], rG=3:4, rq=20:25,icl=T,zstart = 3, seed = 1234, relax = T, zlist=)
adjustedRandIndex(wines[,1],pg2$map)




agree(cn.wines.mixt, givgroup = wines[,1])
agree(cn.wines.kmeans, givgroup = wines[,1])
agree(cn.wines.rpost, givgroup = wines[,1])
agree(cn.wines.rclass, givgroup = wines[,1])

plot(cn.wines.mixt, contours = TRUE)
plot(cn.wines.kmeans, contours = TRUE)
plot(cn.wines.rpost, contours = TRUE)
plot(cn.wines.rclass, contours = TRUE)

if (class(models[[i]])==‘kmeans’){ ariTrain[i] <- adjustedRandIndex(train,models[[i]]\(cluster) ariTest[i] <- adjustedRandIndex(test,models[[i]]\)cluster) bic[i] <- NA icl[i] <- NA G[i] <- length(unique(models[[i]]$cluster))

summaryOfModels <- function(train, test, models){
  nModel <- length(models)
  ariTrain <- NULL
  ariTest <- NULL
  bic <- NULL
  icl <- NULL
  G <- NULL
  for(i in 1:nModel){
           if (class(models[[i]])=='Mclust'){
      ariTrain[i] <- adjustedRandIndex(train,models[[i]]$classification)
      ariTest[i] <- adjustedRandIndex(test,predict(models[[i]],test)$classification)
      bic[i] <- models[[i]]$bic
      icl[i] <- models[[i]]$icl
      G[i] <- models[[i]]$G
    } else if (class(models[[i]])=='ContaminatedMixt'){
      bestModel <- getBestModel(models[[i]])$models[[1]]
      ariTrain[i] <- adjustedRandIndex(train,bestModel$group)
      ariTest[i] <- adjustedRandIndex(test,predict(bestModel,))
      bic[i] <- whichBestModel(models[[i]],train)$BIC
      icl[i] <- whichBestModel(models[[i]],train)$ICL
      G[i] <- whichBestModel(models[[i]],train)$G
    } else if (class(models[[i]])=='teigen'){
      ariTrain[i] <- adjustedRandIndex(train,models[[i]]$iclresults$classification)
      ariTest[i] <- adjustedRandIndex(test,models[[i]]$iclresults$classification)
      bic[i] <- models[[i]]$bic
      icl[i] <- models[[i]]$iclresults$icl
      G[i] <- models[[i]]$G
    } else if (class(models[[i]])=='pgmm'){
      ari[i] <- adjustedRandIndex(df,models[[i]]$map)
      G[i] <- models[[i]]$g
      ifelse(is.null(models[[i]]$bic[1])==TRUE,
             bic[i] <- NA,
             bic[i] <- as.double(models[[i]]$bic[1]))
      ifelse(is.null(models[[i]]$icl[1])==TRUE,
             icl[i] <- NA,
             icl[i] <- as.double(models[[i]]$icl[1]))
    } else if (class(models[[i]])=='tkmeans'){
      ari[i] <- adjustedRandIndex(df,models[[i]]$cluster)
      G[i] <- models[[i]]$k
      bic[i] <- NA
      icl[i] <- NA
    }
  }
  outputDF <- data.frame('AdjustedRandIndex' = ari,
                         'BIC' = bic,
                         'ICL' = icl,
                         'G' = as.integer(G),
                         row.names = c('KMeans','Mclust','ContaminatedMixt','TEigen','PGMM','TClust'),
                         stringsAsFactors = F)
  return(outputDF)
}
test <- summaryOfModels(wines[,1],list(k.means,mixt.wines,mixt.cn.wines,teigen_wine,pippo,trimmedOne))
test
LS0tDQp0aXRsZTogJ011bHRpdmFyaWF0ZSBkYXRhIGFuYWx5c2lzOiBhIGRpc2NyaW1pbmF0aW5nIG1ldGhvZCBvZiB0aGUgb3JpZ2luIG9mIHdpbmVzJw0KYXV0aG9yOiAiTWF0dGVvIEZhc3VsbywgU2ltb25lIEZsYXZpbyBQYXJpcywgTWF0dGVvIFNpdm9jY2lhIg0KZGF0ZTogIjIzLzExLzIwMjEiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQpgYGB7ciBsb2FkRW52LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vTWF0dGVvRmFzdWxvL1JhcmUtRWFydGgvbWFpbi9SL3V0aWwvY29yZUZ1bmN0aW9ucy5SIikNCg0KbG9hZFBhY2thZ2VzKGMoJ3NuJywndGlkeXZlcnNlJywncHN5Y2gnLCdSQ29sb3JCcmV3ZXInLCdzdGFyZ2F6ZXInLCdtY2x1c3QnLCdDb250YW1pbmF0ZWRNaXh0JywNCiAgICAgICAgICAgICAgICdwbG90bHknLCdnZ3Bsb3QyJywnZ2dkZW5kcm8nLCd0ZWlnZW4nLCd0Y2x1c3QnLCdIRE1EJywnY2FUb29scycsJ2NsdXN0dmFyc2VsJywNCiAgICAgICAgICAgICAgICd2c2NjJywnc3BhcmNsJywncGdtbScsJ2NhcmV0JywnZ2xtbmV0JywnTUxtZXRyaWNzJywnRFQnKSkNCg0KbG9hZCgiWjpcXERlc2t0b3BDXFxMVU1TQVxcMlxcRGF0YSBNaW5pbmdcXEZpbml0ZSBNaXh0dXJlXFxGaW5pdGVNaXh0dXJlTDMxLlJEYXRhIikNCnJtKENPMmRhdGEpDQpybShOT2RhdGEpDQpybSh0b25lZGF0YSkNCnR5cGUgPC0gd2luZSRUeXBlDQpybSh3aW5lKQ0KYGBgDQojIEludHJvZHV6aW9uZQ0KRGF0aSBzdSAyNyBjYXJhdHRlcmlzdGljaGUgY2hpbWljby9maXNpY2hlIGRpIHRyZSBkaXZlcnNpIHRpcGkgZGkgdmlubyAoQmFyb2xvLCBHcmlnbm9saW5vLCBCYXJiZXJhKQ0KZGFsIFBpZW1vbnRlLiBVbiBzZXQgZGkgZGF0aSBjb24gMTc4IG9zc2VydmF6aW9uaSBlIDI4IHZhcmlhYmlsaSAoZGkgY3VpIGxhIHByaW1hIHJlbGF0aXZhIGFsbGEgdGlwb2xvZ2lhIGRpIHZpbm8pLiBOZWxsJ29yZGluZTogDQoNCi0gQmFyb2xvIA0KLSBHcmlnbm9saW5vDQotIEJhcmJlcmENCg0KYGBge3IgZHRUYWJsZX0NCmRhdGEod2luZXMpDQp3aW5lcw0KYGBgDQoNCiMjIEFubmF0YSBkZWwgdmlubzogdW4gZmF0dG9yZSBkYSBjb25zaWRlcmFyZT8NCkUnIHN0YXRvIHBvc3NpYmlsZSBhdHRyYXZlcnNvIGxhIHJpY2VyY2Egb3JpZ2luYXJpYSByaXNhbGlyZSBhbGwnYW5ubyBkaSBvc3NlcnZhemlvbmUgZGkgY2lhc2N1biB2aW5vLiBEaSBzZWd1aXRvIHZlbmdvbm8gcmlwb3J0YXRlIGxlIG9zc2VydmF6aW9uaSBkZWkgdHJlIGRpdmVyc2kgdGlwaSBkaSB2aW5vIGR1cmFudGUgZ2xpIGFubmk6DQoNCmBgYHtyIHZpbm9Bbm5pLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KeWVhciA8LSBhcy5udW1lcmljKHN1YnN0cihyb3duYW1lcyh3aW5lcyksIDYsIDcpKQ0KdGFibGUod2luZXMkd2luZSwgeWVhcikNCiN3aW5lc1ssJ3dpbmUnXSA8LSB0eXBlDQpgYGANCk5vdGlhbW8gc3ViaXRvIGNoZSBpbCBCYXJiZXJhIMOoIGRpc3RyaWJ1aXRvIHByaW5jaXBhbG1lbnRlIG5lZ2xpIHVsdGltaSBhbm5pICg3Niw3OCw3OSkgbWVudHJlIGlsIEJhcm9sbyBuZWwgNzEsIDczIGUgNzQuDQpQZXIgcXVhbnRvIHJpZ3VhcmRhIGxhIHBlcmNlbnR1YWxlIGRlbGxlIHNpbmdvbGUgY2xhc3NpOg0KDQpgYGB7ciBuQ2xhc3NpLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kd2luZXMgJT4lDQogIGNvdW50KHdpbmUgPSBmYWN0b3Iod2luZSkpICU+JQ0KICBtdXRhdGUocGN0ID0gcHJvcC50YWJsZShuKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB3aW5lLCB5ID0gcGN0LCBmaWxsID0gd2luZSwgbGFiZWwgPSBzY2FsZXM6OnBlcmNlbnQocGN0KSkpICsgDQogIGdlb21fY29sKHBvc2l0aW9uID0gJ2RvZGdlJykgKyANCiAgZ2VvbV90ZXh0KHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAuOSksDQogICAgICAgICAgICB2anVzdCA9IC0wLjUsIA0KICAgICAgICAgICAgc2l6ZSA9IDMpICsgDQogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIlBlcmNlbnRhZ2UiKSsNCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIldpbmUgTmFtZSIpKw0KICBzY2FsZV9maWxsX2h1ZShsPTQwLCBjPTM1KSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQpFJyBjaGlhcm8gY2hlIGlsIEdyaWdub2xpbm8gc2lhIGlsIHBpw7kgbnVtZXJvc28gKDM5LjklKSBzZWd1aXRvIGRhbCBCYXJvbG8gKDMzLjElKSBlIGRhbCBCYXJiZXJhICgyNy4wJSkuDQoNClBlciByYXBwcmVzZW50YXJlIGxhIGRpc3BlcnNpb25lIGRlaSBkYXRpIGFiYmlhbW8gdXNhdG8gdW5vIHNjYXR0ZXJwbG90IGxlZ2dlcm1lbnRlIGRpZmZlcmVudGUgZGFsIHNvbGl0by4gU3VsbGEgZGlhZ29uYWxlIHN1cGVyaW9yZSBzaSB2ZWRlIGxhIGRpc3RyaWJ1emlvbmUgZGVpIGRhdGkgbWVudHJlIHN1bGxhIGRpYWdvbmFsZSBpbmZlcmlvcmUgdmkgw6ggbGEgX2NvcnJlbGF6aW9uZSBkaSBQZWFyc29uXyB0cmEgbGUgdmFyaWFiaWxpLg0KDQpgYGB7ciBzY2F0dGVyUGxvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgQ29ycmVsYXRpb24gcGFuZWwNCm15X2NvbHMgPC0gYygiIzAwQUZCQiIsICIjRTdCODAwIiwgIiNGQzRFMDciKSANCnBhbmVsLmNvciA8LSBmdW5jdGlvbih4LCB5KXsNCiAgICB1c3IgPC0gcGFyKCJ1c3IiKTsgb24uZXhpdChwYXIodXNyKSkNCiAgICBwYXIodXNyID0gYygwLCAxLCAwLCAxKSkNCiAgICByIDwtIHJvdW5kKGNvcih4LCB5KSwgZGlnaXRzPTIpDQogICAgdHh0IDwtIHBhc3RlMCgiUiA9ICIsIHIpDQogICAgdGV4dCgwLjUsIDAuNSwgdHh0LCBjZXggPSAxKQ0KfQ0KIyBDdXN0b21pemUgdXBwZXIgcGFuZWwNCnVwcGVyLnBhbmVsPC1mdW5jdGlvbih4LCB5KXsNCiAgcG9pbnRzKHgseSwgcGNoID0gMTksIGNvbCA9IG15X2NvbHNbd2luZXMkd2luZV0sY2V4PS41KQ0KfQ0KIyBDcmVhdGUgdGhlIHBsb3RzDQpwYWlycyh3aW5lc1ssMjo4XSwgDQogICAgICBsb3dlci5wYW5lbCA9IHBhbmVsLmNvciwNCiAgICAgIHVwcGVyLnBhbmVsID0gdXBwZXIucGFuZWwpDQpgYGANCkRhbGxlIHByaW1lIDcgdmFyaWFiaWxpLCDDqCBwb3NzaWJpbGUgbm90YXJlIHVuYSBjb3JyZWxhemlvbmUgZGkgMC42OSB0cmEgKmFjaWRpdHkqIGUgKm1hbGljKiBlIG92dmlhbWVudGUgdW5hIHJlbGF6aW9uZSBpbnZlcnNhbWVudGUgcHJvcG9yemlvbmFsZSB0cmEgKnBIKiBlICphY2lkaXR5Ki4NCg0KIyBTdGF0aXN0aWNoZSBkZXNjcml0dGl2ZQ0KUGVyIHZpc3VhbGl6emFyZSBsZSBzdGF0aXN0aWNoZSBkZXNjcml0dGl2ZSAobWVkaWEgZSBkZXZpYXppb25lIHN0YW5kYXJkKSBjaSDDqCBzZW1icmF0byBvcHBvcnR1bm8gZGl2aWRlcmxlIGluIGJhc2UgYWxsYSBjbGFzc2UgZGkgYXBwYXJ0ZW5lbnphOg0KYGBge3IgZGVzY3JpcHRpdmUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwcmludE1lYW5BbmRTZEJ5R3JvdXAgPC0gZnVuY3Rpb24odmFyaWFibGVzLGdyb3VwdmFyaWFibGUpDQogIHsNCiAgICAgdmFyaWFibGVuYW1lcyA8LSBjKG5hbWVzKGdyb3VwdmFyaWFibGUpLG5hbWVzKGFzLmRhdGEuZnJhbWUodmFyaWFibGVzKSkpDQogICAgIGdyb3VwdmFyaWFibGUgPC0gZ3JvdXB2YXJpYWJsZVssMV0NCiAgICAgbWVhbnMgPC0gYWdncmVnYXRlKGFzLm1hdHJpeCh2YXJpYWJsZXMpIH4gZ3JvdXB2YXJpYWJsZSwgRlVOID0gbWVhbikNCiAgICAgbmFtZXMobWVhbnMpIDwtIHZhcmlhYmxlbmFtZXMNCiAgICAgcHJpbnQocGFzdGUoIk1lYW5zOiIpKQ0KICAgICBwcmludChtZWFucykNCiAgICAgc2RzIDwtIGFnZ3JlZ2F0ZShhcy5tYXRyaXgodmFyaWFibGVzKSB+IGdyb3VwdmFyaWFibGUsIEZVTiA9IHNkKQ0KICAgICBuYW1lcyhzZHMpIDwtIHZhcmlhYmxlbmFtZXMNCiAgICAgcHJpbnQocGFzdGUoIlN0YW5kYXJkIGRldmlhdGlvbnM6IikpDQogICAgIHByaW50KHNkcykNCn0NCnByaW50TWVhbkFuZFNkQnlHcm91cCh3aW5lc1syOjI4XSx3aW5lc1sxXSkNCmBgYA0KQWxjdW5lIGNvbnNpZGVyYXppb25pOg0KDQotIExhIG1lZGlhIGRpIHN1Z2FyLCBwb3Rhc3NpdW0sIG1hZ25lc2l1bSwgcGhvc3BoYXRlLCBjaGxvcmlkZSwgZmxhdmFub2lkcywgcHJvYW50aG9jeWFuaW5zLCBjb2xvdXIgbmVsIEJhcm9sbyDDqCBwacO5IGFsdGEuDQotIExhIG1lZGlhIGRpIGFjaWRpdHksIHRhcnRhcmljLCBtYWxpYywgdXJvbmljLCBhbGNhbF9hc2ggbmVsIEJhcmJlcmEgw6ggcGnDuSBhbHRhLg0KLSBMYSBkZXZpYXppb25lIHN0YW5kYXJkIGRpIGFjaWRpdHkgw6ggcGnDuSBhbHRhIG5lbCBHcmlnbm9saW5vLiANCg0KIyMgVmFyaWFuemEgV2l0aGluDQpBYmJpYW1vIGNhbGNvbGF0byBsYSB2YXJpYW56YSB3aXRoaW4gdHJhIHVuYSBmZWF0dXJlIGUgaSB0aXBpIGRpIHZpbm86DQpgYGB7ciB3aXRoaW5WYXJpYW5jZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNhbGNXaXRoaW5Hcm91cHNWYXJpYW5jZSA8LSBmdW5jdGlvbih2YXJpYWJsZSxncm91cHZhcmlhYmxlKQ0KICB7DQogICAgIGdyb3VwdmFyaWFibGUyIDwtIGFzLmZhY3Rvcihncm91cHZhcmlhYmxlW1sxXV0pDQogICAgIGxldmVscyA8LSBsZXZlbHMoZ3JvdXB2YXJpYWJsZTIpDQogICAgIG51bWxldmVscyA8LSBsZW5ndGgobGV2ZWxzKQ0KICAgICBudW10b3RhbCA8LSAwDQogICAgIGRlbm9tdG90YWwgPC0gMA0KICAgICBmb3IgKGkgaW4gMTpudW1sZXZlbHMpDQogICAgIHsNCiAgICAgICAgbGV2ZWxpIDwtIGxldmVsc1tpXQ0KICAgICAgICBsZXZlbGlkYXRhIDwtIHZhcmlhYmxlW2dyb3VwdmFyaWFibGU9PWxldmVsaSxdDQogICAgICAgIGxldmVsaWxlbmd0aCA8LSBsZW5ndGgobGV2ZWxpZGF0YSkNCiAgICAgICAgc2RpIDwtIHNkKGxldmVsaWRhdGEpDQogICAgICAgIG51bWkgPC0gKGxldmVsaWxlbmd0aCAtIDEpKihzZGkgKiBzZGkpDQogICAgICAgIGRlbm9taSA8LSBsZXZlbGlsZW5ndGgNCiAgICAgICAgbnVtdG90YWwgPC0gbnVtdG90YWwgKyBudW1pDQogICAgICAgIGRlbm9tdG90YWwgPC0gZGVub210b3RhbCArIGRlbm9taQ0KICAgICB9DQogICAgIFZ3IDwtIG51bXRvdGFsIC8gKGRlbm9tdG90YWwgLSBudW1sZXZlbHMpDQogICAgIHJldHVybihWdykNCn0NCmNhbGNXaXRoaW5Hcm91cHNWYXJpYW5jZSh3aW5lc1siZmxhdmFub2lkcyJdLHdpbmVzWzFdKQ0KYGBgDQojIyBWYXJpYW56YSBCZXR3ZWVuDQpTdGVzc28gZGlzY29yc28gcGVyIGxhIHZhcmlhbnphIGJldHdlZW4gdHJhIHVuYSBmZWF0dXJlIGUgaSB2aW5pOiANCmBgYHtyIGJldHdlZW5WYXJpYW5jZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNhbGNCZXR3ZWVuR3JvdXBzVmFyaWFuY2UgPC0gZnVuY3Rpb24odmFyaWFibGUsZ3JvdXB2YXJpYWJsZSkNCiAgew0KICAgICBncm91cHZhcmlhYmxlMiA8LSBhcy5mYWN0b3IoZ3JvdXB2YXJpYWJsZVtbMV1dKQ0KICAgICBsZXZlbHMgPC0gbGV2ZWxzKGdyb3VwdmFyaWFibGUyKQ0KICAgICBudW1sZXZlbHMgPC0gbGVuZ3RoKGxldmVscykNCiAgICAgZ3JhbmRtZWFuIDwtIHNhcHBseSh2YXJpYWJsZSxtZWFuKQ0KICAgICBudW10b3RhbCA8LSAwDQogICAgIGRlbm9tdG90YWwgPC0gMA0KICAgICBmb3IgKGkgaW4gMTpudW1sZXZlbHMpDQogICAgIHsNCiAgICAgICAgbGV2ZWxpIDwtIGxldmVsc1tpXQ0KICAgICAgICBsZXZlbGlkYXRhIDwtIHZhcmlhYmxlW2dyb3VwdmFyaWFibGU9PWxldmVsaSxdDQogICAgICAgIGxldmVsaWxlbmd0aCA8LSBsZW5ndGgobGV2ZWxpZGF0YSkNCiAgICAgICAgbWVhbmkgPC0gbWVhbihsZXZlbGlkYXRhKQ0KICAgICAgICBzZGkgPC0gc2QobGV2ZWxpZGF0YSkNCiAgICAgICAgbnVtaSA8LSBsZXZlbGlsZW5ndGggKiAoKG1lYW5pIC0gZ3JhbmRtZWFuKV4yKQ0KICAgICAgICBkZW5vbWkgPC0gbGV2ZWxpbGVuZ3RoDQogICAgICAgIG51bXRvdGFsIDwtIG51bXRvdGFsICsgbnVtaQ0KICAgICAgICBkZW5vbXRvdGFsIDwtIGRlbm9tdG90YWwgKyBkZW5vbWkNCiAgICAgfQ0KICAgICBWYiA8LSBudW10b3RhbCAvIChudW1sZXZlbHMgLSAxKQ0KICAgICBWYiA8LSBWYltbMV1dDQogICAgIHJldHVybihWYikNCn0NCmNhbGNCZXR3ZWVuR3JvdXBzVmFyaWFuY2Uod2luZXNbImZsYXZhbm9pZHMiXSx3aW5lc1sxXSkNCmBgYA0KIyMgU2VwYXJhemlvbmUNClBlciB2ZWRlcmUgcXVhbGkgdmFyaWFiaWxpIGhhbm5vIGxhIG1hZ2dpb3JlIHNlcGFyYXppb25lLCAocmFwcG9ydG8gdHJhIFZhcmlhbnphIEJldHdlZW4gZSBWYXJpYW56YSBXaXRoaW4pIGFiYmlhbW8gc2NyaXR0byB1bmEgZnVuemlvbmUgYXBwb3NpdGEgcGVyIGNhbGNvbGFybmUgaWwgdmFsb3JlIHBlciBvZ25pIGZlYXR1cmUuDQpgYGB7ciBzZXBhcmF0aW9uLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2FsY1NlcGFyYXRpb25zIDwtIGZ1bmN0aW9uKHZhcmlhYmxlcyxncm91cHZhcmlhYmxlKQ0KICB7DQogICAgICMgZmluZCBvdXQgaG93IG1hbnkgdmFyaWFibGVzIHdlIGhhdmUNCiAgICAgdmFyaWFibGVzIDwtIGFzLmRhdGEuZnJhbWUodmFyaWFibGVzKQ0KICAgICBudW12YXJpYWJsZXMgPC0gbGVuZ3RoKHZhcmlhYmxlcykNCiAgICAgIyBmaW5kIHRoZSB2YXJpYWJsZSBuYW1lcw0KICAgICB2YXJpYWJsZW5hbWVzIDwtIGNvbG5hbWVzKHZhcmlhYmxlcykNCiAgICAgIyBjYWxjdWxhdGUgdGhlIHNlcGFyYXRpb24gZm9yIGVhY2ggdmFyaWFibGUNCiAgICAgVncgPC0gTlVMTA0KICAgICBWYiA8LSBOVUxMDQogICAgIHNlcCA8LSBOVUxMDQogICAgIGZvciAoaSBpbiAxOm51bXZhcmlhYmxlcykNCiAgICAgew0KICAgICAgICB2YXJpYWJsZWkgPC0gdmFyaWFibGVzW2ldDQogICAgICAgIHZhcmlhYmxlbmFtZSA8LSB2YXJpYWJsZW5hbWVzW2ldDQogICAgICAgIFZ3W2ldIDwtIGNhbGNXaXRoaW5Hcm91cHNWYXJpYW5jZSh2YXJpYWJsZWksIGdyb3VwdmFyaWFibGUpDQogICAgICAgIFZiW2ldIDwtIGNhbGNCZXR3ZWVuR3JvdXBzVmFyaWFuY2UodmFyaWFibGVpLCBncm91cHZhcmlhYmxlKQ0KICAgICAgICBzZXBbaV0gPC0gVmJbaV0vVndbaV0NCiAgICAgfQ0KICAgICByZXN1bHQgPC0gZGF0YS5mcmFtZSgnV2l0aGluJz1WdywnQmV0d2Vlbic9VmIsJ1NlcCc9c2VwLCByb3cubmFtZXMgPSBhcy52ZWN0b3IoY29sbmFtZXMod2luZXNbLC0xXSkpLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCiAgICAgcmVzdWx0W29yZGVyKHJlc3VsdCRTZXAsZGVjcmVhc2luZyA9IFQpLF0NCn0NCmNhbGNTZXBhcmF0aW9ucyh3aW5lc1syOjI4XSx3aW5lc1sxXSkNCmBgYA0KIyBTcGxpdHRpbmcgaW4gVHJhaW4gZSBUZXN0DQpQZXIgYWxjdW5lIGFuYWxpc2kgc3VjY2Vzc2l2ZSwgYWJiaWFtbyBwcm92YXRvIGEgY2FtYmlhcmUgaWwgdGFzayBkZWxsYSByaWNlcmNhIHRlbnRhbmRvIGRpIHV0aWxpenphcmUgdW4gbW9kZWxsbyBjb21lIGNsYXNzaWZpY2F0b3JlIGUgZGkgbWlzdXJhcm5lIGxlIHByZXN0YXppb25pIGRpIGNsYXNzaWZpY2F6aW9uZS4gQSB0YWxlIHNjb3BvLCBhYmJpYW1vIHN1ZGRpdmlzbyBpbCBkYXRhc2V0IG9yaWdpbmFyaW8gaW4gZHVlIHNvdHRvZ3J1cHBpOiANCg0KLSB0cmFpbg0KLSB0ZXN0DQpgYGB7ciBzcGxpdFRyYWluVGVzdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnJlcXVpcmUoY2FUb29scykNCnNhbXBsZSA9IHNhbXBsZS5zcGxpdCh3aW5lc1ssMV0sIFNwbGl0UmF0aW8gPSAuNTApDQoNCnRyYWluID0gc3Vic2V0KHdpbmVzLCBzYW1wbGUgPT0gVFJVRSkNCnRyYWluVGVzdE5hbWVzIDwtIHRyYWluJHdpbmUNCnByaW50KHBhc3RlKCJUcmFpbiBPYnM6Iixucm93KHRyYWluKSkpDQojdHJhaW4kd2luZSA8LSBhcy5udW1lcmljKHRyYWluJHdpbmUpDQoNCnRlc3QgID0gc3Vic2V0KHdpbmVzLCBzYW1wbGUgPT0gRkFMU0UpDQp3aW5lVGVzdE5hbWVzIDwtIHRlc3Qkd2luZQ0KcHJpbnQocGFzdGUoIlRlc3QgT2JzOiIsbnJvdyh0ZXN0KSkpDQojdGVzdCR3aW5lIDwtIGFzLm51bWVyaWModGVzdCR3aW5lKQ0KYGBgDQojIENsdXN0ZXJpbmcNClBlciBpbCBDbHVzdGVyaW5nIGFiYmlhbW8gZGVjaXNvIGRpIGFwcGxpY2FyZTogDQoNCi0gVW4gYXBwcm9jY2lvICoqRGlzdGFuY2UtQmFzZWQqKjoNCiAgLSBHZXJhcmNoaWNvOg0KICAgIC0gRXVjbGlkZWFuLCBNaW5rb3dza2ksIE1hbmhhdHRhbiwgTWFoYWxhbm9iaXMNCiAgLSBEaSBwYXJ0aXppb25hbWVudG86DQogICAgLSBLTWVhbnMNCiAgICAtIFBBTQ0KLSBVbiBhcHByb2NjaW8gKipNb2RlbC1CYXNlZCoqOg0KICAtIEdhdXNzaWFuIE1peHR1cmUgKE1jbHVzdCkNCiAgLSBDb250YW1pbmF0ZWQgTm9ybWFsIChDTm1peHQpDQogIC0gTXVsdGl2YXJpYXRlIHQgRGlzdHJpYnV0aW9uICh0ZWlnZW4pDQogIC0gUGFyc2ltb25pb3VzIEdhdXNzaWFuIE1peHR1cmUgTW9kZWxzIChwZ21tKQ0KDQoNCiMjIERpc3RhbmNlLUJhc2VkDQpgYGB7ciBkaXN0YW5jZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRpc3NNYXRyaXggICAgPC0gcGFpcndpc2UubWFoYWxhbm9iaXMod2luZXNbLC0xXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBpbmcgPSBjKDE6bnJvdyh3aW5lcykpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3YgPSBjb3Yod2luZXNbLC0xXSkpJGRpc3RhbmNlDQpkaXNzTWF0cml4IDwtIHNxcnQoZGlzc01hdHJpeCkNCmRpc3NNYXRyaXggPC0gYXMuZGlzdChkaXNzTWF0cml4KQ0KY29tYkRpc3QgPC0gZnVuY3Rpb24oZGlzdGFuY2UsIG1ldGhvZHMsIGRmLCBkdCwgZGlzc01hdHJpeCkgew0KICBjIDwtIDANCiAgcmVzdWx0cyA8LSBsaXN0KCkNCiAgZm9yIChpIGluIDE6bGVuZ3RoKGRpc3RhbmNlKSl7DQogICAgaWZlbHNlKGRpc3RhbmNlW2ldID09ICJtaW5rb3dza2kiLA0KICAgICAgICAgICBkaXN0IDwtIGRpc3QoZGYsIG1ldGhvZCA9IGRpc3RhbmNlW2ldLCBwID0gNCksDQogICAgICAgICAgIGlmZWxzZShkaXN0YW5jZVtpXSA9PSAibWFoYWxhbm9iaXMiLA0KICAgICAgICAgICAgICAgICAgZGlzdCA8LSBkaXNzTWF0cml4LA0KICAgICAgICAgICAgICAgICAgZGlzdCA8LSBkaXN0KGRmLCBtZXRob2QgPSBkaXN0YW5jZVtpXSkpKQ0KICAgIGZvciAoaiBpbiAxOmxlbmd0aChtZXRob2RzKSl7DQogICAgICBkZW5kbyA9IGhjbHVzdChkaXN0LCBtZXRob2QgPSBtZXRob2RzW2pdKQ0KICAgICAgZGVuZG8ueCA9IGdnZGVuZHJvZ3JhbShkZW5kbywgcm90YXRlID0gRiwgc2l6ZSA9IDIsIGxlYWZfbGFiZWxzID0gVCwgbGFiZWxzID0gRikgKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3RpdGxlKHBhc3RlKGRpc3RhbmNlW2ldLCcgJyxtZXRob2RzW2pdLHNlcD0nJykpDQogICAgICBmb3IoZWxlbSBpbiAyOjQpew0KICAgICAgICBjbHVzdGVyID0gY3V0cmVlKGRlbmRvLCBrPWVsZW0pDQogICAgICAgIGMgPC0gYyArIDENCiAgICAgICAgcmVzdWx0c1tbY11dIDwtIGxpc3QoZGlzdGFuY2UgPSBkaXN0YW5jZVtpXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gbWV0aG9kc1tqXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBzID0gZWxlbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUgPSB0YWJsZShkdCxjbHVzdGVyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVuZG8gPSBkZW5kby54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBZGp1c3RlZFJhbmRJbmRleCA9IGFkanVzdGVkUmFuZEluZGV4KGR0LGNsdXN0ZXIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0gY2x1c3RlcikNCiAgICAgIH0NCiAgICB9DQogIH0NCiAgcmV0dXJuKHJlc3VsdHMpDQp9DQpyZXN1bHRzIDwtIGNvbWJEaXN0KGMoImV1Y2xpZGVhbiIsICJtYW5oYXR0YW4iLCAibWlua293c2tpIiwibWFoYWxhbm9iaXMiKSwNCiAgICAgICAgICAgICAgICAgICAgYygic2luZ2xlIiwgImNvbXBsZXRlIiwgImF2ZXJhZ2UiLCAid2FyZC5EIiksIHNjYWxlKHdpbmVzWywtMV0pLCB3aW5lc1ssMV0sIGRpc3NNYXRyaXgpDQoNCm9wdGltYWwgPC0gZnVuY3Rpb24ocmVzdWx0cyl7DQogIGJlc3RfcmFuZEluZGV4LmV1ID0gMA0KICBiZXN0X3JhbmRJbmRleC5tYSA9IDANCiAgYmVzdF9yYW5kSW5kZXgubWkgPSAwDQogIGJlc3RfcmFuZEluZGV4Lm1haGEgPSAwDQogIGJlc3RfbW9kZWwuZXUgPSBpbnRlZ2VyKCkNCiAgYmVzdF9tb2RlbC5tYSA9IGludGVnZXIoKQ0KICBiZXN0X21vZGVsLm1pID0gaW50ZWdlcigpDQogIGJlc3RfbW9kZWwubWFoYSA9IGludGVnZXIoKQ0KICBmb3IgKGkgaW4gMTpsZW5ndGgocmVzdWx0cykpew0KICAgIGN1cnJlbnRfcmFuZEluZGV4ID0gcmVzdWx0c1tbaV1dJEFkanVzdGVkUmFuZEluZGV4DQogICAgaWYgKHJlc3VsdHNbW2ldXSRkaXN0YW5jZSA9PSAiZXVjbGlkZWFuIil7DQogICAgICBpZiAoY3VycmVudF9yYW5kSW5kZXggPiBiZXN0X3JhbmRJbmRleC5ldSkgew0KICAgICAgICBiZXN0X3JhbmRJbmRleC5ldSA9IGN1cnJlbnRfcmFuZEluZGV4DQogICAgICAgIGJlc3RfbW9kZWwuZXUgPSBpDQogICAgICB9DQogICAgfQ0KICAgIGVsc2UgaWYgKHJlc3VsdHNbW2ldXSRkaXN0YW5jZSA9PSAibWFuaGF0dGFuIil7DQogICAgICBpZiAoY3VycmVudF9yYW5kSW5kZXggPiBiZXN0X3JhbmRJbmRleC5tYSkgew0KICAgICAgICBiZXN0X3JhbmRJbmRleC5tYSA9IGN1cnJlbnRfcmFuZEluZGV4DQogICAgICAgIGJlc3RfbW9kZWwubWEgPSBpDQogICAgICB9DQogICAgfQ0KICAgIGVsc2UgaWYgKHJlc3VsdHNbW2ldXSRkaXN0YW5jZSA9PSAibWlua293c2tpIil7DQogICAgICBpZiAoY3VycmVudF9yYW5kSW5kZXggPiBiZXN0X3JhbmRJbmRleC5taSkgew0KICAgICAgICBiZXN0X3JhbmRJbmRleC5taSA9IGN1cnJlbnRfcmFuZEluZGV4DQogICAgICAgIGJlc3RfbW9kZWwubWkgPSBpDQogICAgICB9DQogICAgfQ0KICAgIGVsc2UgaWYgKHJlc3VsdHNbW2ldXSRkaXN0YW5jZSA9PSAibWFoYWxhbm9iaXMiKXsNCiAgICAgIGlmIChjdXJyZW50X3JhbmRJbmRleCA+IGJlc3RfcmFuZEluZGV4Lm1haGEpIHsNCiAgICAgICAgYmVzdF9yYW5kSW5kZXgubWFoYSA9IGN1cnJlbnRfcmFuZEluZGV4DQogICAgICAgIGJlc3RfbW9kZWwubWFoYSA9IGkNCiAgICAgIH0NCiAgICB9DQogIH0NCiAgI3ByaW50KGxpc3QoZXVjbGlkZWFuID0gbGlzdChtb2RlbC5udW1iZXIgPSBiZXN0X21vZGVsLmV1LA0KICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSByZXN1bHRzW1tiZXN0X21vZGVsLmV1XV0kZ3JvdXBzLA0KICAjICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFkanVzdGVkUmFuZEluZGV4ID0gYmVzdF9yYW5kSW5kZXguZXUpLA0KICAjICAgICAgICAgICBtYW5oYXR0YW4gPSBsaXN0KG1vZGVsLm51bWJlciA9IGJlc3RfbW9kZWwubWEsDQogICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHJlc3VsdHNbW2Jlc3RfbW9kZWwubWFdXSRncm91cHMsDQogICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWRqdXN0ZWRSYW5kSW5kZXggPSBiZXN0X3JhbmRJbmRleC5tYSksDQogICMgICAgICAgICAgIG1pbmtvd3NraSA9IGxpc3QobW9kZWwubnVtYmVyID0gYmVzdF9tb2RlbC5taSwNCiAgIyAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0gcmVzdWx0c1tbYmVzdF9tb2RlbC5taV1dJGdyb3VwcywNCiAgIyAgICAgICAgICAgICAgICAgICAgICAgICAgICBBZGp1c3RlZFJhbmRJbmRleCA9IGJlc3RfcmFuZEluZGV4Lm1pKSwNCiAgIyAgICAgICAgICAgbWFoYWxhbm9iaXM9bGlzdChtb2RlbC5udW1iZXIgPSBiZXN0X21vZGVsLm1haGEsDQogICMgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IHJlc3VsdHNbW2Jlc3RfbW9kZWwubWFoYV1dJGdyb3VwcywNCiAgIyAgICAgICAgICAgICAgICAgICAgICAgICAgICBBZGp1c3RlZFJhbmRJbmRleCA9IGJlc3RfcmFuZEluZGV4Lm1haGEpKQ0KICAjICAgICkNCiAgcmV0dXJuKGxpc3QoZXVjbGlkZWFuID0gcmVzdWx0c1tbYmVzdF9tb2RlbC5ldV1dLA0KICAgICAgICAgICAgICBtYW5oYXR0YW4gPSByZXN1bHRzW1tiZXN0X21vZGVsLm1hXV0sDQogICAgICAgICAgICAgIG1pbmtvd3NraSA9IHJlc3VsdHNbW2Jlc3RfbW9kZWwubWldXSwNCiAgICAgICAgICAgICAgbWFoYWxhbm9iaXM9cmVzdWx0c1tbYmVzdF9tb2RlbC5tYWhhXV0pKQ0KfQ0KYmVzdF9kaXN0X21vZGVsID0gb3B0aW1hbChyZXN1bHRzKQ0KYGBgDQojIyMgRGVuZHJvZ3JhbW1pIHsudGFic2V0fQ0KDQojIyMjIEV1Y2xpZGVhbg0KYGBge3IgZXVjbGlkZWFuLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90bHkoYmVzdF9kaXN0X21vZGVsJGV1Y2xpZGVhbiRkZW5kbykNCnByaW50KGJlc3RfZGlzdF9tb2RlbCRldWNsaWRlYW4kdGFibGUpDQpwcmludChwYXN0ZSgiQWRqdXN0ZWRSYW5kSW5kZXg6Iixyb3VuZChiZXN0X2Rpc3RfbW9kZWwkZXVjbGlkZWFuJEFkanVzdGVkUmFuZEluZGV4LDMpKSkNCmBgYA0KIyMjIyBNYW5oYXR0YW4NCmBgYHtyIG1hbmhhdHRhbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdncGxvdGx5KGJlc3RfZGlzdF9tb2RlbCRtYW5oYXR0YW4kZGVuZG8pDQpwcmludChiZXN0X2Rpc3RfbW9kZWwkbWFuaGF0dGFuJHRhYmxlKQ0KcHJpbnQocGFzdGUoIkFkanVzdGVkUmFuZEluZGV4OiIscm91bmQoYmVzdF9kaXN0X21vZGVsJG1hbmhhdHRhbiRBZGp1c3RlZFJhbmRJbmRleCwzKSkpDQpgYGANCiMjIyMgTWlua293c2tpDQpgYGB7ciBtaW5rb3dza2ksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3RseShiZXN0X2Rpc3RfbW9kZWwkbWlua293c2tpJGRlbmRvKQ0KcHJpbnQoYmVzdF9kaXN0X21vZGVsJG1pbmtvd3NraSR0YWJsZSkNCnByaW50KHBhc3RlKCJBZGp1c3RlZFJhbmRJbmRleDoiLHJvdW5kKGJlc3RfZGlzdF9tb2RlbCRtaW5rb3dza2kkQWRqdXN0ZWRSYW5kSW5kZXgsMykpKQ0KYGBgDQojIyMjIE1haGFsYW5vYmlzDQpgYGB7ciBtYWhhbGFub2JpcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdncGxvdGx5KGJlc3RfZGlzdF9tb2RlbCRtYWhhbGFub2JpcyRkZW5kbykNCnByaW50KGJlc3RfZGlzdF9tb2RlbCRtYWhhbGFub2JpcyR0YWJsZSkNCnByaW50KHBhc3RlKCJBZGp1c3RlZFJhbmRJbmRleDoiLHJvdW5kKGJlc3RfZGlzdF9tb2RlbCRtYWhhbGFub2JpcyRBZGp1c3RlZFJhbmRJbmRleCwzKSkpDQpgYGANCg0KIyMjIEstTWVhbnMgZSBQQU0gey50YWJzZXR9DQojIyMjIEtNZWFucyAzDQpgYGB7ciBrbWVhbnMzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcmVxdWlyZShjbHVzdGVyKQ0Kay5tZWFucy4zIDwtIGttZWFucyhzY2FsZSh3aW5lc1ssLTFdKSxjZW50ZXJzPTMsbnN0YXJ0ID0gNTAsIGl0ZXIubWF4ID0gMTAwKQ0KdGFibGUod2luZXNbLDFdLCBrLm1lYW5zLjMkY2x1c3RlcikNCnByaW50KHBhc3RlKCJBZGp1c3RlZFJhbmRJbmRleDoiLHJvdW5kKGFkanVzdGVkUmFuZEluZGV4KGsubWVhbnMuMyRjbHVzdGVyLCB3aW5lc1ssMV0pLDMpKSkNCmBgYA0KIyMjIyBLTWVhbnMgNA0KYGBge3Iga21lYW5zNCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnJlcXVpcmUoY2x1c3RlcikNCmsubWVhbnMuNCA8LSBrbWVhbnMoc2NhbGUod2luZXNbLC0xXSksY2VudGVycz00LG5zdGFydCA9IDUwLCBpdGVyLm1heCA9IDEwMCkNCnRhYmxlKHdpbmVzWywxXSwgay5tZWFucy40JGNsdXN0ZXIpDQpwcmludChwYXN0ZSgiQWRqdXN0ZWRSYW5kSW5kZXg6Iixyb3VuZChhZGp1c3RlZFJhbmRJbmRleChrLm1lYW5zLjQkY2x1c3Rlciwgd2luZXNbLDFdKSwzKSkpDQpgYGANCiMjIyMgUEFNIDMNCmBgYHtyIHBhbTMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpyZXF1aXJlKGNsdXN0ZXIpDQpQQU0uMyA8LSBwYW0od2luZXNbLC0xXSwgaz0zLA0KICAgIG1ldHJpYyA9ICJldWNsaWRlYW4iLCANCiAgICBuc3RhcnQgPSA1MCwNCiAgICBzdGFuZCA9IFRSVUUpDQp0YWJsZSh3aW5lc1ssMV0sIFBBTS4zJGNsdXN0ZXJpbmcpDQpwcmludChwYXN0ZSgiQWRqdXN0ZWRSYW5kSW5kZXg6Iixyb3VuZChhZGp1c3RlZFJhbmRJbmRleChQQU0uMyRjbHVzdGVyaW5nLCB3aW5lc1ssMV0pLDMpKSkNCmBgYA0KIyMjIyBQQU0gNA0KYGBge3IgcGFtNCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnJlcXVpcmUoY2x1c3RlcikNClBBTS40IDwtIHBhbSh3aW5lc1ssLTFdLCBrPTQsDQogICAgbWV0cmljID0gImV1Y2xpZGVhbiIsIA0KICAgIG5zdGFydCA9IDUwLA0KICAgIHN0YW5kID0gVFJVRSkNCnRhYmxlKHdpbmVzWywxXSwgUEFNLjQkY2x1c3RlcmluZykNCnByaW50KHBhc3RlKCJBZGp1c3RlZFJhbmRJbmRleDoiLHJvdW5kKGFkanVzdGVkUmFuZEluZGV4KFBBTS40JGNsdXN0ZXJpbmcsIHdpbmVzWywxXSksMykpKQ0KYGBgDQoNCiMgVmFyaWFibGUgU2VsZWN0aW9uIA0KDQojIyBQcmluY2lwaW8gZmlsb3NvZmljbyB7LnRhYnNldH0NCl9Ob3ZhY3VsYSBPY2NhbWk6IGZydXN0cmEgZml0IHBlciBwbHVyYSBxdW9kIHBvdGVzdCBmaWVyaSBwZXIgcGF1Y2lvcmFfIChJbCByYXNvaW8gZGkgT2NjYW06IMOoIGZ1dGlsZSBmYXJlIGNvbiBwacO5IG1lenppIGNpw7IgY2hlIHNpIHB1w7IgZmFyZSBjb24gbWVubykuIFRhbGUgcHJpbmNpcGlvIG1ldG9kb2xvZ2ljbyDDqCByaXRlbnV0byBhbGxhIGJhc2UgZGVsIHBlbnNpZXJvIHNjaWVudGlmaWNvIG1vZGVybm8uDQoNCiMjIyBIZWFkbG9uZw0KYGBge3IgaGVhZGxvbmcsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdWJzZXQuaGVhZGxvbmcgPC0gY2x1c3R2YXJzZWwod2luZXNbLC0xXSxHPTM6NCwgc2VhcmNoID0gJ2hlYWRsb25nJywgZGlyZWN0aW9uID0gJ2ZvcndhcmQnLCBwYXJhbGxlbCA9IFQsIHZlcmJvc2UgPSBGKQ0KDQpoZWFkbG9uZy5zZWxlY3RlZCA8LSBzdWJzZXQuaGVhZGxvbmckbW9kZWwNCnRhYmxlKHdpbmVzWywxXSxoZWFkbG9uZy5zZWxlY3RlZCRjbGFzc2lmaWNhdGlvbikNCnByaW50KHBhc3RlKCJBZGp1c3RlZFJhbmRJbmRleDoiLHJvdW5kKGFkanVzdGVkUmFuZEluZGV4KGhlYWRsb25nLnNlbGVjdGVkJGNsYXNzaWZpY2F0aW9uLCB3aW5lc1ssMV0pLDMpKSkNCmBgYA0KIyMjIEdyZWVkeQ0KYGBge3IgR3JlZWR5LCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3Vic2V0LmdyZWVkeSA8LSBjbHVzdHZhcnNlbCh3aW5lc1ssLTFdLEc9Mzo0LCBzZWFyY2ggPSAnZ3JlZWR5JywgZGlyZWN0aW9uID0gJ2ZvcndhcmQnLCBwYXJhbGxlbCA9IFQsIHZlcmJvc2UgPSBGKQ0KDQpncmVlZHkuc2VsZWN0ZWQgPC0gc3Vic2V0LmdyZWVkeSRtb2RlbA0KdGFibGUod2luZXNbLDFdLGdyZWVkeS5zZWxlY3RlZCRjbGFzc2lmaWNhdGlvbikNCnByaW50KHBhc3RlKCJBZGp1c3RlZFJhbmRJbmRleDoiLHJvdW5kKGFkanVzdGVkUmFuZEluZGV4KGdyZWVkeS5zZWxlY3RlZCRjbGFzc2lmaWNhdGlvbiwgd2luZXNbLDFdKSwzKSkpDQpgYGANCiMjIyBWU0NDDQpgYGB7ciBWU0NDLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdnNjYy5tY2x1c3QgPC0gdnNjYyh3aW5lc1ssLTFdLCBHPTM6NCwgYXV0b21hdGUgPSAibWNsdXN0IiwgaW5pdGlhbCA9IE5VTEwsIHRyYWluID0gTlVMTCwgZm9yY2VyZWR1Y3Rpb24gPSBUUlVFKQ0KYGBgDQoNCmBgYHtyIFZTQ0NSRVNVTFQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0YWJsZSh3aW5lc1ssMV0sIHZzY2MubWNsdXN0JGluaXRpYWxydW4kY2xhc3NpZmljYXRpb24pICNDbHVzdGVyaW5nIHJlc3VsdHMgb24gZnVsbCBkYXRhIHNldA0KcHJpbnQocGFzdGUoIkFkanVzdGVkUmFuZEluZGV4OiIscm91bmQoYWRqdXN0ZWRSYW5kSW5kZXgodnNjYy5tY2x1c3QkaW5pdGlhbHJ1biRjbGFzc2lmaWNhdGlvbiwgd2luZXNbLDFdKSwzKSkpDQp0YWJsZSh3aW5lc1ssMV0sIHZzY2MubWNsdXN0JGJlc3Rtb2RlbCRjbGFzc2lmaWNhdGlvbikgI0NsdXN0ZXJpbmcgcmVzdWx0cyBvbiByZWR1Y2VkIGRhdGEgc2V0DQpwcmludChwYXN0ZSgiQWRqdXN0ZWRSYW5kSW5kZXg6Iixyb3VuZChhZGp1c3RlZFJhbmRJbmRleCh2c2NjLm1jbHVzdCRiZXN0bW9kZWwkY2xhc3NpZmljYXRpb24sIHdpbmVzWywxXSksMykpKQ0KYGBgDQojIyMgS01lYW5zU3BhcnNlIDMNCmBgYHtyIGttc3BhcnNlMywgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmttLnBlcm0uMyA8LSBLTWVhbnNTcGFyc2VDbHVzdGVyLnBlcm11dGUod2luZXNbLC0xXSxLPTMsd2JvdW5kcz1zZXEoMyw3LGxlbj0xNSksbnBlcm1zPTUwLHNpbGVudCA9IFQpDQoNCmttLnNwYXJzZS4zIDwtIEtNZWFuc1NwYXJzZUNsdXN0ZXIod2luZXNbLC0xXSxLPTMsd2JvdW5kcz1rbS5wZXJtLjMkYmVzdHcsbnN0YXJ0ID0gNTAsIHNpbGVudCA9IFQsIG1heGl0ZXI9MTAwKQ0KdGFibGUod2luZXNbLDFdLGttLnNwYXJzZS4zW1sxXV0kQ3MpDQpwcmludChwYXN0ZSgiQWRqdXN0ZWRSYW5kSW5kZXg6Iixyb3VuZChhZGp1c3RlZFJhbmRJbmRleChrbS5zcGFyc2UuM1tbMV1dJENzLCB3aW5lc1ssMV0pLDMpKSkNCmBgYA0KIyMjIEtNZWFuc1NwYXJzZSA0DQpgYGB7ciBrbXNwYXJzZTQsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQprbS5wZXJtLjQgPC0gS01lYW5zU3BhcnNlQ2x1c3Rlci5wZXJtdXRlKHdpbmVzWywtMV0sSz00LHdib3VuZHM9c2VxKDMsNyxsZW49MTUpLG5wZXJtcz01MCxzaWxlbnQgPSBUKQ0KDQprbS5zcGFyc2UuNCA8LSBLTWVhbnNTcGFyc2VDbHVzdGVyKHdpbmVzWywtMV0sSz00LHdib3VuZHM9a20ucGVybS40JGJlc3R3LG5zdGFydCA9IDUwLCBzaWxlbnQgPSBULCBtYXhpdGVyPTEwMCkNCnRhYmxlKHdpbmVzWywxXSxrbS5zcGFyc2UuNFtbMV1dJENzKQ0KcHJpbnQocGFzdGUoIkFkanVzdGVkUmFuZEluZGV4OiIscm91bmQoYWRqdXN0ZWRSYW5kSW5kZXgoa20uc3BhcnNlLjRbWzFdXSRDcywgd2luZXNbLDFdKSwzKSkpDQpgYGANCg0KDQojIyBNb2RlbC1CYXNlZA0KYGBge3IgbW9kZWxCYXNlZCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1peHQud2luZXMgPC0gTWNsdXN0KHdpbmVzWywtMV0sRz0zOjgpDQoNCmNuLndpbmVzLm1peHQgPC0gQ05taXh0KHdpbmVzWywtMV0sIEcgPSAzLCBpbml0aWFsaXphdGlvbiA9ICJtaXh0Iiwgc2VlZCA9IDEyMzQsIHBhcmFsbGVsID0gRiwgdmVyYm9zZSA9IEYpDQpjbi53aW5lcy5rbWVhbnMgPC0gQ05taXh0KHdpbmVzWywtMV0sIEcgPSAzLCBpbml0aWFsaXphdGlvbiA9ICJrbWVhbnMiLCBzZWVkID0gMTIzNCwgcGFyYWxsZWwgPSBGLCB2ZXJib3NlID0gRikNCmNuLndpbmVzLnJwb3N0IDwtIENObWl4dCh3aW5lc1ssLTFdLCBHID0gMywgaW5pdGlhbGl6YXRpb24gPSAicmFuZG9tLnBvc3QiLCBzZWVkID0gMTIzNCwgcGFyYWxsZWwgPSBGLCB2ZXJib3NlID0gRikNCmNuLndpbmVzLnJjbGFzcyA8LSBDTm1peHQod2luZXNbLC0xXSwgRyA9IDMsIGluaXRpYWxpemF0aW9uID0gInJhbmRvbS5jbGFzIiwgc2VlZCA9IDEyMzQsIHBhcmFsbGVsID0gRiwgdmVyYm9zZSA9IEYpDQoNCg0KdGVpZ2VuLmttZWFucyA8LSB0ZWlnZW4od2luZXNbLC0xXSwgR3M9Mzo0LCBpbml0ID0gJ2ttZWFucycsIHNjYWxlID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSx0ZWlnZW4ua21lYW5zJGNsYXNzaWZpY2F0aW9uKQ0KDQp0ZWlnZW4uY2xhc3NpZmllci5rbWVhbnMgPC0gdGVpZ2VuKHRyYWluWywtMV0sIEdzPTM6NCwgaW5pdCA9ICdrbWVhbnMnLCBzY2FsZSA9IFQsIGtub3duID0gdHJhaW5bLDFdKQ0KcHJlZGljdCh0ZWlnZW4uY2xhc3NpZmllci5rbWVhbnMsdGVzdFssLTFdKQ0KYWRqdXN0ZWRSYW5kSW5kZXgodGVzdFssMV0scHJlZGljdCh0ZWlnZW4uY2xhc3NpZmllci5rbWVhbnMsIHRlc3RbLC0xXSkkY2xhc3NpZmljYXRpb24pDQoNCnRlaWdlbi5jbGFzc2lmaWVyLnVuaWZvcm0gPC0gdGVpZ2VuKHRyYWluWywtMV0sIEdzPTM6NCwgaW5pdCA9ICd1bmlmb3JtJywgc2NhbGUgPSBULCBrbm93biA9IHRyYWluWywxXSkNCnByZWRpY3QodGVpZ2VuLmNsYXNzaWZpZXIudW5pZm9ybSx0ZXN0WywtMV0pDQphZGp1c3RlZFJhbmRJbmRleCh0ZXN0WywxXSxwcmVkaWN0KHRlaWdlbi5jbGFzc2lmaWVyLnVuaWZvcm0sIHRlc3RbLC0xXSkkY2xhc3NpZmljYXRpb24pDQoNCnRlaWdlbi5oYXJkIDwtIHRlaWdlbih3aW5lc1ssLTFdLCBHcz0zOjQsIGluaXQgPSAnaGFyZCcsIHNjYWxlID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSx0ZWlnZW4uaGFyZCRjbGFzc2lmaWNhdGlvbikNCnRlaWdlbi5zb2Z0IDwtIHRlaWdlbih3aW5lc1ssLTFdLCBHcz0zOjQsIGluaXQgPSAnc29mdCcsIHNjYWxlID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSx0ZWlnZW4uc29mdCRjbGFzc2lmaWNhdGlvbikNCmBgYA0KIyBUZWNuaWNoZSBkaSByZWdvbGFyaXp6YXppb25lDQpQZXIgbGEgbm9zdHJhIGFuYWxpc2kgYWJiaWFtbyB2b2x1dG8gdmVyaWZpY2FyZSBsJ2VmZmljaWVuemEgZGkgdHJlIG5vdGkgbW9kZWxsaSBkaSByZWdvbGFyaXp6YXppb25lIGF0dHJhdmVyc28gaWwgcGFjY2hldHRvICpjYXJldCo6DQoNCi0gUmlkZ2UNCi0gTGFzc28NCi0gRWxhc3RpYyBOZXQNCg0KYGBge3IgbGFtYmRhVHVuaW5nLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGFtYmRhIDwtIDEwXnNlcSgwLCAtMiwgbGVuZ3RoID0gMjUwKQ0KYGBgDQoNCiMjIFJpZGdlDQpJbCBtb2RlbGxvICpSaWRnZSogcmlkdWNlIGkgY29lZmZpY2llbnRpLCBpbiBtb2RvIGNoZSBsZSB2YXJpYWJpbGksIGNvbiB1biBjb250cmlidXRvIG1pbm9yZSBhbCByaXN1bHRhdG8sIGFiYmlhbm8gaSBsb3JvIGNvZWZmaWNpZW50aSB2aWNpbmkgYWxsbyB6ZXJvLiBJbnZlY2UgZGkgZm9yemFybGkgYSBlc3NlcmUgZXNhdHRhbWVudGUgemVybyAoY29tZSBuZWwgKkxhc3NvKiksIGxpIHBlbmFsaXp6aWFtbyBjb24gdW4gdGVybWluZSBjaGlhbWF0byAqbm9ybWEgTDIqIGNvc3RyaW5nZW5kb2xpIGNvc8OsIGEgZXNzZXJlIHBpY2NvbGkuIEluIHF1ZXN0byBtb2RvIGRpbWludWlhbW8gbGEgY29tcGxlc3NpdMOgIGRlbCBtb2RlbGxvIHNlbnphIGVsaW1pbmFyZSBuZXNzdW5hIHZhcmlhYmlsZSBhdHRyYXZlcnNvIHVuYSBjb3N0YW50ZSBjaGlhbWF0YSBsYW1iZGEgKCRcbGFtYmRhJCkgZGkgcGVuYWxpenphemlvbmU6DQokJA0KTF97cmlkZ2V9KFxoYXR7XGJldGF9KSA9IFxzdW1fe2kgPSAxfV57bn17KHlfaSAtIHhfaVxoYXR7XGJldGF9KV4yfSArIFxsYW1iZGFcc3VtX3trID0gMX1ee0t9e1xoYXR7XGJldGF9X2teMn0NCiQkDQoNCmBgYHtyIGNhcmV0UmlkZ2UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIEJ1aWxkIHRoZSBtb2RlbA0Kc2V0LnNlZWQoMTIzKQ0KcmlkZ2UgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gdHJhaW5bLC0xXSwNCiAgeSA9IGZhY3Rvcih0cmFpblssMV0pLA0KICBtZXRob2QgPSAiZ2xtbmV0IiwNCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKCJjdiIsIG51bWJlciA9IDEwLCBjbGFzc1Byb2JzID0gVFJVRSwgc3VtbWFyeUZ1bmN0aW9uID0gbXVsdGlDbGFzc1N1bW1hcnkpLA0KICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKGFscGhhID0gMCwgbGFtYmRhPWxhbWJkYSksDQogIG1ldHJpYz0iQWNjdXJhY3kiKQ0KIyBNb2RlbCBjb2VmZmljaWVudHMNCmNvZWYocmlkZ2UkZmluYWxNb2RlbCwgcmlkZ2UkYmVzdFR1bmUkbGFtYmRhKQ0KIyBNYWtlIHByZWRpY3Rpb25zDQpwcmVkaWN0aW9ucy5yaWRnZSA8LSByaWRnZSAlPiUgcHJlZGljdCh0ZXN0KQ0KIyBNb2RlbCBwcmVkaWN0aW9uIHBlcmZvcm1hbmNlDQp0aWJibGUoDQogIHRydWVWYWx1ZSA9IHdpbmVUZXN0TmFtZXMsDQogIHByZWRpY3RlZFZhbHVlID0gcHJlZGljdGlvbnMucmlkZ2UpDQpgYGANCklsIHJpZGdlIMOoIGNvbXBvc3RvIGRhbGxhIHNvbW1hIGRlaSByZXNpZHVpIHF1YWRyYXRpIHBpw7kgdW5hIHBlbmFsaXTDoCwgZGVmaW5pdGEgZGFsbGEgbGV0dGVyYSBMYW1iZGEsIGNoZSDDqCBtb2x0aXBsaWNhdGEgcGVyIGxhIHNvbW1hIGRlaSBjb2VmZmljaWVudGkgcXVhZHJhdGkgJFxiZXRhJC4gUXVhbmRvICRcbGFtYmRhID0gMCQsIGlsIHRlcm1pbmUgZGkgcGVuYWxpdMOgIG5vbiBoYSBhbGN1biBlZmZldHRvIGUgaWwgcmlkZ2UgcHJvZHVycsOgIGkgY29lZmZpY2llbnRpIG1pbmltaSBxdWFkcmF0aSBjbGFzc2ljaS4gVHV0dGF2aWEsIHF1YW5kbyAkXGxhbWJkYSQgYXVtZW50YSBhbGzigJlpbmZpbml0bywgbOKAmWltcGF0dG8gZGVsbGEgcGVuYWxpdMOgIGF1bWVudGEgZSBpIGNvZWZmaWNpZW50aSBzaSBhdnZpY2luYW5vIGFsbG8gemVyby4gSWwgcmlkZ2Ugw6ggcGFydGljb2xhcm1lbnRlIGluZGljYXRvIHF1YW5kbyBzaSBoYW5ubyBtb2x0aSBkYXRpIG11bHRpdmFyaWF0aSBjb24gbnVtZXJvIGRpIGZlYXR1cmUgbWFnZ2lvcmUgZGVsIG51bWVybyBkaSBvc3NlcnZhemlvbmkuIExvIHN2YW50YWdnaW8sIHBlcsOyLCDDqCBjaGUgaW5jbHVkZXLDoCB0dXR0aSBsZSBmZWF0dXJlIG5lbCBtb2RlbGxvIGZpbmFsZSwgYSBkaWZmZXJlbnphIGRlaSBtZXRvZGkgZGkgZmVhdHVyZSBzZWxlY3Rpb24sIGNoZSBnZW5lcmFsbWVudGUgc2VsZXppb25lcmFubm8gdW4gaW5zaWVtZSByaWRvdHRvIGRpIHZhcmlhYmlsaSB0cmEgcXVlbGxlIGRpc3BvbmliaWxpLg0KYGBge3IgcmlkZ2VSZXN1bHR9DQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLnJpZGdlLCB0ZXN0JHdpbmUpDQpgYGANCg0KIyMgTGFzc28NCklsICpMZWFzdCBBYnNvbHV0ZSBTaHJpbmthZ2UgYW5kIFNlbGVjdGlvbiBPcGVyYXRvciogKExBU1NPKSByaWR1Y2UgaSBjb2VmZmljaWVudGkgdmVyc28gbG8gemVybyBwZW5hbGl6emFuZG8gaWwgbW9kZWxsbyBjb24gdW4gdGVybWluZSBkaSBwZW5hbGl0w6AgY2hpYW1hdG8gKm5vcm1hIEwxKiwgY2hlIMOoIGxhIHNvbW1hIGRlaSBjb2VmZmljaWVudGkgaW4gdmFsb3JlIGFzc29sdXRvOg0KJCQNCkxfe2xhc3NvfShcaGF0e1xiZXRhfSkgPSBcc3VtX3tpID0gMX1ee259eyh5X2kgLSB4X2lcaGF0e1xiZXRhfSleMn0gKyBcbGFtYmRhXHN1bV97ayA9IDF9XntLfXt8XGhhdHtcYmV0YX1fa3x9DQokJA0KYGBge3IgY2FyZXRMYXNzbywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgQnVpbGQgdGhlIG1vZGVsDQpzZXQuc2VlZCgxMjMpDQpsYXNzbyA8LSBjYXJldDo6dHJhaW4oDQogIHggPSB0cmFpblssLTFdLA0KICB5ID0gZmFjdG9yKHRyYWluWywxXSksDQogIG1ldGhvZCA9ICJnbG1uZXQiLA0KICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2woImN2IiwgbnVtYmVyID0gMTAsIGNsYXNzUHJvYnMgPSBUUlVFLCBzdW1tYXJ5RnVuY3Rpb24gPSBtdWx0aUNsYXNzU3VtbWFyeSksDQogIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoYWxwaGEgPSAxLCBsYW1iZGE9bGFtYmRhKSwNCiAgbWV0cmljPSJBY2N1cmFjeSIpDQojIE1vZGVsIGNvZWZmaWNpZW50cw0KY29lZihsYXNzbyRmaW5hbE1vZGVsLCBsYXNzbyRiZXN0VHVuZSRsYW1iZGEpDQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWRpY3Rpb25zLmxhc3NvIDwtIGxhc3NvICU+JSBwcmVkaWN0KHRlc3QpDQojIE1vZGVsIHByZWRpY3Rpb24gcGVyZm9ybWFuY2UNCnRpYmJsZSgNCiAgdHJ1ZVZhbHVlID0gd2luZVRlc3ROYW1lcywNCiAgcHJlZGljdGVkVmFsdWUgPSBwcmVkaWN0aW9ucy5sYXNzbykNCmBgYA0KSW4gcXVlc3RvIGNhc28gbGEgcGVuYWxpdMOgIGhhIGzigJllZmZldHRvIGRpIGZvcnphcmUgYWxjdW5lIGRlbGxlIHN0aW1lIGRlaSBjb2VmZmljaWVudGksIGNvbiB1biBjb250cmlidXRvIG1pbm9yZSBhbCBtb2RlbGxvLCBhIGVzc2VyZSBlc2F0dGFtZW50ZSB1Z3VhbGUgYSB6ZXJvLiBJbCBsYXNzbywgcXVpbmRpLCBwdcOyIGFuY2hlIGVzc2VyZSB2aXN0byBjb21lIHVu4oCZYWx0ZXJuYXRpdmEgYWkgbWV0b2RpIGRpIGZlYXR1cmUgc2VsZWN0aW9uIHBlciBlc2VndWlyZSBsYSBzZWxlemlvbmUgZGVsbGUgdmFyaWFiaWxpIGFsIGZpbmUgZGkgcmlkdXJyZSBsYSBjb21wbGVzc2l0w6AgZGVsIG1vZGVsbG8uDQoNCkNvbWUgbmVsIHJpZGdlLCDDqCBmb25kYW1lbnRhbGUgc2VsZXppb25hcmUgdW4gYnVvbiB2YWxvcmUgZGkgJFxsYW1iZGEkLg0KDQpRdWFuZG8gbGFtYmRhIMOoIHBpY2NvbG8sIGlsIHJpc3VsdGF0byDDqCBtb2x0byB2aWNpbm8gYWxsYSBzdGltYSBkZWkgbWluaW1pIHF1YWRyYXRpLiBBbGzigJlhdW1lbnRhcmUgZGkgbGFtYmRhLCBzaSB2ZXJpZmljYSB1bmEgY29udHJhemlvbmUgaW4gbW9kbyBkYSBwb3RlciBlbGltaW5hcmUgbGUgdmFyaWFiaWxpIGNoZSBzb25vIGEgemVyby4NCmBgYHtyIGxhc3NvUmVzdWx0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucy5sYXNzbywgdGVzdCR3aW5lKQ0KYGBgDQoNCiMjIEVsYXN0aWMgTmV0DQpFbGFzdGljIE5ldCBjb21iaW5hIGxlIHByb3ByaWV0w6AgZGkgUmlkZ2UgZSBMYXNzbyBwZW5hbGl6emFuZG8gaWwgbW9kZWxsbyB1c2FuZG8gc2lhIGxhIG5vcm1hIEwyIGNoZSBsYSBub3JtYSBMMToNCiQkDQpMX3tFbGFzdGljTmV0fShcaGF0e1xiZXRhfSkgPSBcZnJhY3tcc3VtX3tpID0gMX1ee259eyh5X2kgLSB4X2lcaGF0e1xiZXRhfSleMn19ezJufSArIFxsYW1iZGEoXGZyYWN7MS1cYWxwaGF9ezJ9XHN1bV97ayA9IDF9XntLfXtcaGF0e1xiZXRhfV9rXjJ9ICsgXGFscGhhXHN1bV97ayA9IDF9XntLfXt8XGhhdHtcYmV0YX1fa318KQ0KJCQNCmBgYHtyIGNhcmV0RWxhc3RpYywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDEyMykNCmVsYXN0aWMgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gdHJhaW5bLC0xXSwNCiAgeSA9IGZhY3Rvcih0cmFpblssMV0pLA0KICBtZXRob2QgPSAiZ2xtbmV0IiwNCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKCJjdiIsIG51bWJlciA9IDEwLCBjbGFzc1Byb2JzID0gVFJVRSwgc3VtbWFyeUZ1bmN0aW9uID0gbXVsdGlDbGFzc1N1bW1hcnkpLA0KICBtZXRyaWM9IkFjY3VyYWN5IikNCiMgTW9kZWwgY29lZmZpY2llbnRzDQpjb2VmKGVsYXN0aWMkZmluYWxNb2RlbCwgZWxhc3RpYyRiZXN0VHVuZSRsYW1iZGEpDQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWRpY3Rpb25zLmVuZXQgPC0gZWxhc3RpYyAlPiUgcHJlZGljdCh0ZXN0KQ0KIyBNb2RlbCBwcmVkaWN0aW9uIHBlcmZvcm1hbmNlDQp0aWJibGUoDQogIHRydWVWYWx1ZSA9IHdpbmVUZXN0TmFtZXMsDQogIHByZWRpY3RlZFZhbHVlID0gcHJlZGljdGlvbnMuZW5ldCkNCmBgYA0KT2x0cmUgYSBpbXBvc3RhcmUgZSBzY2VnbGllcmUgdW4gdmFsb3JlIGxhbWJkYSwgbOKAmSplbGFzdGljIG5ldCogY2kgY29uc2VudGUgYW5jaGUgZGkgb3R0aW1penphcmUgaWwgcGFyYW1ldHJvIGFsZmEgZG92ZSAkXGFscGhhID0gMCQgY29ycmlzcG9uZGUgYSAqcmlkZ2UqIGUgJFxhbHBoYSA9IDEkIGFsICpsYXNzbyouDQoNClBlcnRhbnRvIHBvc3NpYW1vIHNjZWdsaWVyZSB1biB2YWxvcmUgJFxhbHBoYSQgY29tcHJlc28gdHJhIDAgZSAxIHBlciBvdHRpbWl6emFyZSBs4oCZZWxhc3RpYyBuZXQuIFNlIHRhbGUgdmFsb3JlIMOoIGluY2x1c28gaW4gcXVlc3RvIGludGVydmFsbG8sIHNpIGF2csOgIHVuYSByaWR1emlvbmUgY29uIGFsY3VuaSBwb3J0YXRpIGEgJDAkLg0KDQpgYGB7ciBlbmV0UmVzdWx0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucy5lbmV0LCB0ZXN0JHdpbmUpDQpgYGANCg0KIyMgU3VtbWFyeSBkZWxsZSBwcmVzdGF6aW9uaQ0KYGBge3IgY29uZk1hdHJpeCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1vZGVscyA8LSBsaXN0KHJpZGdlID0gY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucy5yaWRnZSwgdGVzdCR3aW5lKSwgDQogICAgICAgICAgICAgICBsYXNzbyA9IGNhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnMubGFzc28sdGVzdCR3aW5lKSwNCiAgICAgICAgICAgICAgIGVsYXN0aWMgPSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLmVuZXQsdGVzdCR3aW5lKSkNCiNtb2RlbHMgJT4lIHN1bW1hcnkobWV0cmljID0gIkFjY3VyYWN5IikNCmBgYA0KDQojIFBhcnNpbW9uaW91cyBHYXVzc2lhbiBNaXh0dXJlIE1vZGVscw0KYGBge3IgYWR2YW5jZWQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIElsIHJpbGFzc2FtZW50byBkYXRvIGRhIChwLXEpXjIgPiBwK3Egw6ggdmVyaWZpY2F0byBwZXIgIDEgPD0gcSA8PSAyMA0KcGcucmFuZG9tLmNjYyA8LSBwZ21tRU0oc2VsZWN0ZWRXaW5lc1ssLTFdLCByRz0zOjQsIHJxPTE6NSwgc2VlZCA9IDEyMzQsIHJlbGF4ID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSxwZy5yYW5kb20uY2NjJG1hcCkNCg0KcGcucmFuZG9tIDwtIHBnbW1FTSh3aW5lc1ssLTFdLCByRz0zOjQsIHJxPTIwOjI1LGljbD1GLHpzdGFydCA9IDEsY2NjU3RhcnQgPSBGLGxvb3AgPSA1MCwgc2VlZCA9IDEyMzQsIHJlbGF4ID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSxwZy5yYW5kb20uY2NjJG1hcCkNCg0KcGcyIDwtIHBnbW1FTSh3aW5lc1ssLTFdLCByRz0zOjQsIHJxPTIwOjI1LGljbD1ULHpzdGFydCA9IDEsY2NjU3RhcnQgPSBULGxvb3AgPSA1MCwgc2VlZCA9IDEyMzQsIHJlbGF4ID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSxwZzIkbWFwKQ0KDQpwZzIgPC0gcGdtbUVNKHdpbmVzWywtMV0sIHJHPTM6NCwgcnE9MjA6MjUsaWNsPVQsenN0YXJ0ID0gMiwgc2VlZCA9IDEyMzQsIHJlbGF4ID0gVCkNCmFkanVzdGVkUmFuZEluZGV4KHdpbmVzWywxXSxwZzIkbWFwKQ0KDQpwZzIgPC0gcGdtbUVNKHdpbmVzWywtMV0sIHJHPTM6NCwgcnE9MjA6MjUsaWNsPVQsenN0YXJ0ID0gMywgc2VlZCA9IDEyMzQsIHJlbGF4ID0gVCwgemxpc3Q9KQ0KYWRqdXN0ZWRSYW5kSW5kZXgod2luZXNbLDFdLHBnMiRtYXApDQoNCg0KDQoNCmFncmVlKGNuLndpbmVzLm1peHQsIGdpdmdyb3VwID0gd2luZXNbLDFdKQ0KYWdyZWUoY24ud2luZXMua21lYW5zLCBnaXZncm91cCA9IHdpbmVzWywxXSkNCmFncmVlKGNuLndpbmVzLnJwb3N0LCBnaXZncm91cCA9IHdpbmVzWywxXSkNCmFncmVlKGNuLndpbmVzLnJjbGFzcywgZ2l2Z3JvdXAgPSB3aW5lc1ssMV0pDQoNCnBsb3QoY24ud2luZXMubWl4dCwgY29udG91cnMgPSBUUlVFKQ0KcGxvdChjbi53aW5lcy5rbWVhbnMsIGNvbnRvdXJzID0gVFJVRSkNCnBsb3QoY24ud2luZXMucnBvc3QsIGNvbnRvdXJzID0gVFJVRSkNCnBsb3QoY24ud2luZXMucmNsYXNzLCBjb250b3VycyA9IFRSVUUpDQpgYGANCg0KaWYgKGNsYXNzKG1vZGVsc1tbaV1dKT09J2ttZWFucycpew0KICAgICAgYXJpVHJhaW5baV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgodHJhaW4sbW9kZWxzW1tpXV0kY2x1c3RlcikNCiAgICAgIGFyaVRlc3RbaV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgodGVzdCxtb2RlbHNbW2ldXSRjbHVzdGVyKQ0KICAgICAgYmljW2ldIDwtIE5BDQogICAgICBpY2xbaV0gPC0gTkENCiAgICAgIEdbaV0gPC0gbGVuZ3RoKHVuaXF1ZShtb2RlbHNbW2ldXSRjbHVzdGVyKSkNCg0KDQpgYGB7ciBzdW1tYXJ5T2ZBbGxNb2RlbHMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzdW1tYXJ5T2ZNb2RlbHMgPC0gZnVuY3Rpb24odHJhaW4sIHRlc3QsIG1vZGVscyl7DQogIG5Nb2RlbCA8LSBsZW5ndGgobW9kZWxzKQ0KICBhcmlUcmFpbiA8LSBOVUxMDQogIGFyaVRlc3QgPC0gTlVMTA0KICBiaWMgPC0gTlVMTA0KICBpY2wgPC0gTlVMTA0KICBHIDwtIE5VTEwNCiAgZm9yKGkgaW4gMTpuTW9kZWwpew0KICAgICAgICAgICBpZiAoY2xhc3MobW9kZWxzW1tpXV0pPT0nTWNsdXN0Jyl7DQogICAgICBhcmlUcmFpbltpXSA8LSBhZGp1c3RlZFJhbmRJbmRleCh0cmFpbixtb2RlbHNbW2ldXSRjbGFzc2lmaWNhdGlvbikNCiAgICAgIGFyaVRlc3RbaV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgodGVzdCxwcmVkaWN0KG1vZGVsc1tbaV1dLHRlc3QpJGNsYXNzaWZpY2F0aW9uKQ0KICAgICAgYmljW2ldIDwtIG1vZGVsc1tbaV1dJGJpYw0KICAgICAgaWNsW2ldIDwtIG1vZGVsc1tbaV1dJGljbA0KICAgICAgR1tpXSA8LSBtb2RlbHNbW2ldXSRHDQogICAgfSBlbHNlIGlmIChjbGFzcyhtb2RlbHNbW2ldXSk9PSdDb250YW1pbmF0ZWRNaXh0Jyl7DQogICAgICBiZXN0TW9kZWwgPC0gZ2V0QmVzdE1vZGVsKG1vZGVsc1tbaV1dKSRtb2RlbHNbWzFdXQ0KICAgICAgYXJpVHJhaW5baV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgodHJhaW4sYmVzdE1vZGVsJGdyb3VwKQ0KICAgICAgYXJpVGVzdFtpXSA8LSBhZGp1c3RlZFJhbmRJbmRleCh0ZXN0LHByZWRpY3QoYmVzdE1vZGVsLCkpDQogICAgICBiaWNbaV0gPC0gd2hpY2hCZXN0TW9kZWwobW9kZWxzW1tpXV0sdHJhaW4pJEJJQw0KICAgICAgaWNsW2ldIDwtIHdoaWNoQmVzdE1vZGVsKG1vZGVsc1tbaV1dLHRyYWluKSRJQ0wNCiAgICAgIEdbaV0gPC0gd2hpY2hCZXN0TW9kZWwobW9kZWxzW1tpXV0sdHJhaW4pJEcNCiAgICB9IGVsc2UgaWYgKGNsYXNzKG1vZGVsc1tbaV1dKT09J3RlaWdlbicpew0KICAgICAgYXJpVHJhaW5baV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgodHJhaW4sbW9kZWxzW1tpXV0kaWNscmVzdWx0cyRjbGFzc2lmaWNhdGlvbikNCiAgICAgIGFyaVRlc3RbaV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgodGVzdCxtb2RlbHNbW2ldXSRpY2xyZXN1bHRzJGNsYXNzaWZpY2F0aW9uKQ0KICAgICAgYmljW2ldIDwtIG1vZGVsc1tbaV1dJGJpYw0KICAgICAgaWNsW2ldIDwtIG1vZGVsc1tbaV1dJGljbHJlc3VsdHMkaWNsDQogICAgICBHW2ldIDwtIG1vZGVsc1tbaV1dJEcNCiAgICB9IGVsc2UgaWYgKGNsYXNzKG1vZGVsc1tbaV1dKT09J3BnbW0nKXsNCiAgICAgIGFyaVtpXSA8LSBhZGp1c3RlZFJhbmRJbmRleChkZixtb2RlbHNbW2ldXSRtYXApDQogICAgICBHW2ldIDwtIG1vZGVsc1tbaV1dJGcNCiAgICAgIGlmZWxzZShpcy5udWxsKG1vZGVsc1tbaV1dJGJpY1sxXSk9PVRSVUUsDQogICAgICAgICAgICAgYmljW2ldIDwtIE5BLA0KICAgICAgICAgICAgIGJpY1tpXSA8LSBhcy5kb3VibGUobW9kZWxzW1tpXV0kYmljWzFdKSkNCiAgICAgIGlmZWxzZShpcy5udWxsKG1vZGVsc1tbaV1dJGljbFsxXSk9PVRSVUUsDQogICAgICAgICAgICAgaWNsW2ldIDwtIE5BLA0KICAgICAgICAgICAgIGljbFtpXSA8LSBhcy5kb3VibGUobW9kZWxzW1tpXV0kaWNsWzFdKSkNCiAgICB9IGVsc2UgaWYgKGNsYXNzKG1vZGVsc1tbaV1dKT09J3RrbWVhbnMnKXsNCiAgICAgIGFyaVtpXSA8LSBhZGp1c3RlZFJhbmRJbmRleChkZixtb2RlbHNbW2ldXSRjbHVzdGVyKQ0KICAgICAgR1tpXSA8LSBtb2RlbHNbW2ldXSRrDQogICAgICBiaWNbaV0gPC0gTkENCiAgICAgIGljbFtpXSA8LSBOQQ0KICAgIH0NCiAgfQ0KICBvdXRwdXRERiA8LSBkYXRhLmZyYW1lKCdBZGp1c3RlZFJhbmRJbmRleCcgPSBhcmksDQogICAgICAgICAgICAgICAgICAgICAgICAgJ0JJQycgPSBiaWMsDQogICAgICAgICAgICAgICAgICAgICAgICAgJ0lDTCcgPSBpY2wsDQogICAgICAgICAgICAgICAgICAgICAgICAgJ0cnID0gYXMuaW50ZWdlcihHKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSBjKCdLTWVhbnMnLCdNY2x1c3QnLCdDb250YW1pbmF0ZWRNaXh0JywnVEVpZ2VuJywnUEdNTScsJ1RDbHVzdCcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KICByZXR1cm4ob3V0cHV0REYpDQp9DQp0ZXN0IDwtIHN1bW1hcnlPZk1vZGVscyh3aW5lc1ssMV0sbGlzdChrLm1lYW5zLG1peHQud2luZXMsbWl4dC5jbi53aW5lcyx0ZWlnZW5fd2luZSxwaXBwbyx0cmltbWVkT25lKSkNCnRlc3QNCmBgYA0K